本文由 伯乐在线 - xxmen 翻译,Alick 校稿。未经许可,禁止转载!
英文出处:Dmitri Gribenko。欢迎加入翻译组。
这篇文章的目的是让每个程序员(特别是 C 程序员)说:我真的不懂 C。我想要让大家看到 C 语言的那些阴暗角落比我们想象中更近,甚至那些平常的代码中就包含着未定义的行为。
这篇文章设置了一系列的问题和答案。所有的例子都是从源代码中单独分离出来的。
1.
1 2 |
|
Q:这段代码正确吗?是否会因为变量被定义了两次而导致错误的出现?注意这是源于同一个源码文件,而不是函数体或代码段的一部分。
A:是的,这段代码是正确的。第一行是临时的定义直到编译器处理了第二行的定义之后才成为正式的“定义”。
2.
1 2 3 4 5 6 7 8 9 10 11 |
|
Q: 这样写的结果是即使 x 是空指针 bar() 函数都会被调用,并且程序不会崩溃。这是否是优化器的错误,或者全部是正确的?
A: 全部都是正确的。如果 x 是空指针,未定义的行为出现在第 (1) 行, 没有人欠程序员什么,所以程序并不会在第 (1) 行崩溃, 也不会试图在第 (2) 行返回假如已经成功运行第 (1) 行。让我们来探讨编译器遵循的规则,它都按如下的方式进行。在对第 (1) 行的分析之后,编译器认为 x 不会是一个空指针,于是第 (2) 行和 第 (3) 行就被认定为是没用的代码。变量 y 被当做没用的变量去除。从内存中读取的操作也会被去除,因为 *x 并不符合易变类型(volatile)。
这就是无用的变量如何导致空指针检查失效的例子。
3.有这样一个函数:
1 2 3 4 5 6 7 8 9 |
|
有人想要按如下方式来优化它:
1 2 3 4 5 6 7 8 9 |
|
Q:调用原始的函数和调用优化后的函数,对于变量 zp 是否有可能获得不同的结果?
A:这是可能的,当 yp == zp 时结果就不同。
4.
1 2 3 4 5 |
|
Q: 这个函数是否可能返回最大下界(inf) ?假设浮点数运算是按照IEEE 754 标准(大部分机器遵循)执行的, 并且断言语句是可用的(NDEBUG 并没有被定义)。
A:是的,这是可以的。通过传入一个非规范化的 x 的值,比如 1e-309.
5.
1 2 3 4 5 6 7 8 9 10 |
|
Q: 上面提供的函数应该返回以空终止字符结尾的字符串长度,找出其中存在的一个 bug 。
A: 使用 int 类型来存储对象的大小是错误的,因为无法保证 int 类型能够存下任何对象的大小,应该使用 size_t。
6.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Q: 这个循环是死循环。这是为什么?
A: size_t 是无符号类型。 如果 i 是无符号类型, 那么 i >= 0 永远都是正确的。
7.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
这个程序分别用两个不同的编译器编译并且在一台小字节序的机器上运行。获得了如下两种不同的结果:
1 2 |
|
Q:你如何解释第二种结果?
A:所给程序存在未定义的行为。程序违反了编译器的强重叠规则(strict aliasing)。虽然 int 在第 (2) 行被改变了,但是编译器可以假设任何的 long 都没有改变。我们不能间接引用那些和其他不兼容类型指针相重名的指针。这就是编译器之所以可以传递和在第一行的执行过程中被读取的相同的 long (第(3)行)的原因。
8.
1 2 3 4 5 6 |
|
Q: 这个代码是否是正确的?如果不存在未定义行为,那么它会输出什么?
A: 是的, 这里使用了逗号运算符。首先,逗号左边的参数被计算后丢弃,然后,右边的参数经过计算后被当做整个运算符的值使用,所以输出是 10 2 10。
注意在函数调用中的逗号符号(比如 f(a(), b()))并不是逗号运算符,因此也就不会保证运算的顺序,a() 和 b() 会以随机的顺序计算。
9.
1 2 3 4 |
|
Q: 函数 add(UINT_MAX, 1) 的结果是什么?
A:对于无符号数的溢出结果是有定义的,结果是 2^(CHAR_BIT * sizeof(unsigned int)) ,所以函数 add 的结果是 0 。
10.
1 2 3 4 |
|
Q:函数 add(INT_MAX, 1) 的结果是什么?
A:有符号整数的溢出结果是未定义的行为。
11.
1 2 3 4 |
|
Q:这里是否可能出现未定义的行为?如果是的话,是在输入什么参数时发生的?
A:neg(INT_MIN)。如果 ECM 用附加码(补码)表示负整数, 那么 INT_MIN 的绝对值比 INT_MAX 的绝对值大一。在这种情况下,-INT_MIN 造成了有符号整数的溢出,这是一种未定义的行为。
12.
1 2 3 4 5 |
|
Q:这里是否可能出现未定义的行为?如果是的话,是在什么参数上发生的?
A:如果 ECM 用附加码表示负数, 那么 div(INT_MIN, -1) 导致了与上一个例子相同的问题。
全能程序员交流QQ群290551701,群内程序员都是来自,百度、阿里、京东、小米、去哪儿、饿了吗、蓝港等高级程序员 ,拥有丰富的经验。加入我们,直线沟通技术大牛,最佳的学习环境,了解业内的一手的资讯。如果你想结实大牛,那 就加入进来,让大牛带你超神!