C 参考手册
- C 语言
- C 的历史
- 基本概念
- 表达式
- 声明
- 初始化
- 函数
- 语句
- 静态断言
- 字符常量
- 函数声明
- 函数定义
- 转义序列
- 翻译阶段
- 标识符
- 作用域
- 生存期
- 查找与命名空间
- ASCII 码表
- 类型
- 遵从性
- 算术类型
- restrict 类型限定符
- 类型
- 对象与对齐
- 主函数
- 未定义行为
- 内存模型
- if 语句
- switch 语句
- for 循环
- while 循环
- do-while 循环
- continue语句
- break 语句
- goto语句
- return 语句
- 值类别
- 求值顺序
- 整数常量
- 浮点常量
- 字符串字面量
- 复合字面量
- 常量表达式
- 隐式转换
- 成员访问运算符
- 逻辑运算符
- 比较运算符
- 算术运算符
- 赋值运算符
- 自增/自减运算符
- 其他运算符
- sizeof 运算符
- _Alignof 运算符
- 转型运算符
- C 运算符优先级
- 泛型选择
- 标量初始化
- 数组初始化
- 结构体与联合体初始化
- 指针声明
- 数组声明
- 枚举
- 存储类指定符
- const 类型限定符
- volatile 类型限定符
- 结构体声明
- 联合体声明
- 位域
- _Alignas
- typedef 声明
- 原子类型
- 外部及试探性定义
- inline 函数指定符
- _Noreturn 函数指定符
- 变长参数
- 内联汇编
- 可分析性
- 替用运算符及记号
- C 关键词
- 预处理器
- C 标准库头文件
- 类型支持
- 程序支持工具
- 变参数函数
- 错误处理
- 动态内存管理
- 日期和时间工具
- 字符串库
- 算法
- 数值
- 文件输入/输出
- 本地化支持
- 原子操作库
- 线程支持库
- 实验性 C 标准库
- 有用的资源
- 符号索引
- 注释
未定义行为
C 语言标准精确指定了 C 语言程序的可观察行为,除了下列分类之一:
- 未定义行为 - 程序的该行为没有限制。未定义行为的例子是越过数组边界的访问、有符号整数溢出、空指针解引用、在表达式中超过一次修改标量而其中无顺序点、通过不同类型的指针访问对象,等等。编译器不要求诊断未定义行为(尽管多数简单情形是得到诊断的),且编译后的程序不要求做任何有意义的事。
- 实现定义行为 - 在未指定行为之上,实现规范了如何选择。例如,字节中的位数,或有符号整数右移是算术还是逻辑。
(注意:严格遵从的程序不依赖任何未指定、未定义或实现定义行为)
要求编译器对违背任何 C 语法规则或语义约束的任何程序发布诊断消息(错误或警告),即使其行为被指定为未定义或实现定义,或者编译器可提供语言扩展以允许此种程序被接受。另外,不要求对未定义行为诊断。
UB 与优化
因为正确的 C 程序是没有未定义行为的,编译器可以在启用优化的条件下编译确实有 UB 的程序时,生成不期待的结果:
例如,
有符号溢出
int foo(int x) { return x+1 > x; // 真或为有符号溢出导致的 UB }
可以编译成(演示)
foo: movl $1, %eax ret
越界访问
int table[4] = {0}; int exists_in_table(int v) { // 在最初的 4 个迭代中返回真或因为越界访问 UB for (int i = 0; i <= 4; i++) { if (table[i] == v) return 1; } return 0; }
可以编译成(演示)
exists_in_table: movl $1, %eax ret
未初始化标量
可能产生下列输出(可在一个旧版本 gcc 观察到):
p is true p is false
可以编译成(演示)
f: mov eax, 42 ret
非法标量
int f(void) { _Bool b = 0; unsigned char* p =(unsigned char*)&b; *p = 10; // 从 b 读取现在是 UB return b == 0; }
可编译成(演示)
f(): movl $11, %eax ret
空指针解引用
int foo(int* p) { int x = *p; if(!p) return x; // 为上述 UB ,或绝不采用此分支 else return 0; } int bar() { int* p = NULL; return *p; // 无条件 UB }
可以编译成( foo 用 gcc , bar 用 clang )
foo: xorl %eax, %eax ret bar: retq
访问传递给 realloc 的指针
选择 Clang 以观察示出的输出
运行此代码
可能的输出:
12
无副效应的无限循环
选择 Clang 以观察示出的输出
运行此代码
#include <stdio.h> int fermat() { const int MAX = 1000; int a=1,b=1,c=1; // 无副效应的无限循环是 UB while (1) { if (((a*a*a) == ((b*b*b)+(c*c*c)))) return 1; a++; if (a>MAX) { a=1; b++; } if (b>MAX) { b=1; c++; } if (c>MAX) { c=1;} } return 0; } int main(void) { if (fermat()) puts("Fermat's Last Theorem has been disproved."); else puts("Fermat's Last Theorem has not been disproved."); }
可能的输出:
Fermat's Last Theorem has been disproved.
引用
- C11 standard (ISO/IEC 9899:2011):
- 3.4 Behavior (p: 3-4)
- 4/2 Undefined behavior (p: 8)
- C99 standard (ISO/IEC 9899:1999):
- 3.4 Behavior (p: 3-4)
- 4/2 Undefined behavior (p: 7)
- C89/C90 standard (ISO/IEC 9899:1990):
- 1.6 DEFINITIONS OF TERMS
外部链接
- 每个 C 程序员都该知道关于未定义行为的事 #1/3
- 每个 C 程序员都该知道关于未定义行为的事 #2/3
- 每个 C 程序员都该知道关于未定义行为的事 #3/3
- 未定义行为能导致时间旅行(在所有事项中,时间旅行可是最惊人的)
- 了解 C/C++ 中的整数溢出
- 未定义行为及费马最后定理
- 空指针的趣事, part 1 ( Linux 2.6.30 中由空指针解引用导致的未定义行为所引发的局部滥用)