C语言的未定义行为(undefined behaviour)
什么是未定义行为
简单地说,未定义行为是指C语言标准未做规定的行为。编译器可能不会报错,但是这些行为编译器会自行处理,所以不同的编译器会出现不同的结果,什么都有可能发生,这是一个极大的隐患,所以我们应该尽量避免这种情况的发生。
特征
包含多个不确定的副作用的代码的行为总是被认为未定义。(简单而言, “多 个不确定副作用” 是指在同一个表达式中使用导致同一对象修改两次或修改以后 又被引用的自增, 自减和赋值操作符的任何组合。这是一个粗略的定义)
例子
第一例(同一个表达式中有多种运算符)
在同一个表达式中多种运算符一起计算的时候,即使我们知道各符号都有自己的优先级或者是人为的加上括号限制计算顺序,但是我们却不知道编译器会先计算哪一段,计算顺序完全取决于编译器,所以结果并不一定按照我们预想中的输出。 代码段一
int i=7; printf(“%d”, i++*i++);
编译器可以选择使用变量的旧值相乘以后再对二者进行自增运算。没有任何保证确保自增或自减会在输出变量原值之后和对表达式的其它部分进行计算之前立即进行。
代码段二
int a=5,b; b=++a*–a;
b的值不能确定
代码段三
int i = 5; int j = (++i) + (++i) + (++i);
j的值不能确定
代码段四
#include <stdio.h> int main(){ int i = 0; int a[] = { 10,20,30}; int r = 1 * a[i++] + 2 * a[i++] + 3 * a[i++]; printf("%d ", r); return 0; }
这段代码也并不是我们想象中的那样按照优先级来计算,编译器选择了他自己的一种套路, 此段代码详细实现情况请戳链接:
第二例(同一语句中各参数的求值顺序)
在同一语句中,有多个表达式,我们不能确定编译器先调用哪一个表达式进行运算,运算之后又会对另一个表达式产生影响,因为他不一定是按照我们想象中自左向右进行调用的。 代码段一
printf("%d,%d ",++n,power(2,n));
代码段二
int f(int a, int b); int i = 5; f(++i, ++i);
第三例(通过指针修改const常量的值)
编译器对于向常量所在内存赋值这件事的处理是未定义的。即在对常量的内存操作也许并不是我们想象的那样。 代码段一
int main() { const int a = 1; int *b = (int*)&a; *b = 21; printf("%d, %d", a, *b); return 0; }
该段代码的详细实现请戳链接:
总结
个人认为,未定义行为实在难以确切的分类出来,幽默一点的说就是,水实在很深,所以我只是简单了解一下,更多的情况也只有日后再编程过程中慢慢积累。前辈的文章也着实很详细,看了之后基本上就会有一个大致的了解了。
练习题
解析:根据上面的总结,A选项,我的理解是我们不知道编译器会怎么选择自增和赋值的顺序,所以这是由编译器决定的,属于未定义行为。B选项,”hello“这个字符串属于一个字符串常量了,指针p指向了这个字符串常量,下一语句通过这个指针来直接修改常量第二个字符,这也属于未定义行为。选项C,只是通过指针找到第二个字符并将它赋值给一个字符变量,并没有改变这个字符串常量,所以不属于未定义行为。选项D,在printf语句中,i++和i–谁先执行由编译器决定,这是未定义行为。故此题选C。