- 陷进
- 1.1 = 不同于 ==
- √1.2 & 与 | 不同于 && 与 ||
如果你是一个程序员并且开发中真正用到C语言来解决生活中的复杂问题,你一定要仔细阅读这骗文章。即使你已经是一个C语言的专家级程序员,仍然有必要阅读这篇文章,等你阅读完后会不禁感叹:“我的天,就在前不久我还遇到了这样一个BUG!”
- 请思考下面这段代码
#include
int main()
{
int i;
int arr[12];
for(i=0;i<=12;i++)
{
arr[i]=0;
printf("bit\n");
}
return 0;
}
这段代码的作用是初始化int型数组arr的10个元素。但是在很多编译器中,他将会陷入一个死循环!
我在这里是使用的VS2019编译器进行的测试
C语言使用符号 = 作为赋值运算,符号 == 作为比较、一般而言。赋值运算相对于比较运算使用的更为频繁,因此符号 = 被定义为一种 *** 作符,因而重复进行赋值 *** 作(如:a=b=c,相当于b=c;a=b;)可以很容易书写,并且赋值 *** 作还可以被嵌入到更大的表达式中,不过这也导致了一个潜在问题,当程序员本意是作比较运算时,却可能无意中误写成了赋值运算。
比如下例,该语句本意是要检查x是否等于y:
int x=1;
int y =2;
if(x = y)
{
printf("x等于y");
}
实际上是把y的值赋给了x,然后检查该赋值语句是否为0.
再看下面一个例子 本例子中循坏语句的本意是跳过文件中的水平制表符、空格符、换行号:
char c;
while(c = '\t' || c == ' ' || c == '\n')
{
c=getchar();
}
由于在比较字符 ‘ ’ 和变量c时,误将比较运算符 == 写成了赋值运算符 = 。因为赋值运算符 = 的优先级要低于逻辑运算符 || ,因此实际上是将以下表达式的值赋值给了c:
'\t' || c == ' ' || c == '\n'
因为‘\t’不等于0('\t’的ASCII码值为9),那么无论变量c此前为何值,上述表达式最终结果为都是1,因此循坏将一直继续下去,从而成为一个死循环。
某些C编译器在发现形如a = b的表达式出现在循坏语句的条件判断部分时,会给出警告以提醒程序员。
当确实需要对变量进行赋值并检查该变量的新值是否为0时,我们应该写成下例代码,进行明显的比较:
int x = 0;
int y = 1;
if((x = y) != 0)
{
printf("hehe");
}
而不应该写成下例代码:
int x = 0;
int y = 1;
if( x = y)
{
printf("hehe");
}
另外,如果把赋值运算误写成比较运算,同样也会造成混淆:
int F = 0;
if(F == open(argv[i],0)) < 0)
{
printf("hehe");
}
如果open函数执行成功,,将返回0或者正数,而执行失败,将返回-1。上面这例代码是将open的返回值赋给变量F中,然后然后通过比较F是否小于0来检查open是否执行成功。但是,此处的 == 本应该是 =。而按照上面代码的写法,实际进行的 *** 作是比较函数open的返回值与变量F,然后检查比较的结果是否小于0。因为比较运算符 == 的结果只能是0或者是1,永远不可能小于0,所以printf函数不可能被调用。如果执行此例代码,似乎允许程序一切正常,某些编译器在遇到这种情况会警告与0比较无效,但是作为程序员不应该指望靠编译器来提醒,而且并不是所有编译器都具备这样的功能。
C语言中有两类逻辑运算符,某些时候可以互换,按位运算符:按位与 & 、按位或 | 和 按位去反~(是按2进制位);以及逻辑运算符 :逻辑与&& 、逻辑或 || 和逻辑非 !。如果程序员用其中一类的某个运算符替换掉另一类中对应的运算符,也许会大吃一惊拍一下自己的大腿说:“艾玛我去,这么牛逼的代码谁写的啊”。互换之后程序看上去还能“正常”工作,但是实际上这只是巧合所致。
按位运算符&、| 、和 ~ 对 *** 作数的处理方式是将其看作一个二进制的位序列,分别对其每个二进位进行 *** 作。例如:10&12的结果是8(二进制表示为1000),因为运算符&按 *** 作数的二进制表示逐位比较10(二进制为1010)和12(二进制为1100),当且仅当两个 *** 作数的二进制表示的某位上同时是1,最后结果的二进制表示中该位才是1.同样道理,10 | 12的结果是14(二进制1110),而 ~ 10的结果是11(二进制11…110101),至少在以二进制补码表示负数的机器上是这个结果。
另一方面,逻辑运算符&&、|| 和 !对 *** 作数的处理方式是将其视作要么是“真”,要么是“假”。通常约定将0视作“假”,而非0视作“真”.这些运算符当结果为“真”时返回1,当结果为“假”时返回0,它们只可能返回0或1.而且,运算符&&和运算符 || 在左侧 *** 作数的值能够确定最终结果时根本不会对右侧 *** 作数求值。
因此,我们能够很容易求得这个表达式的结果:!10的结果是0,因为10是非0; 10&&12的结果是1,因为10和12都不是0; 10 lI 12的结果也是1,因为10不是0,而且,12根本不会被求值;在表达式10lf()中,f)也不会被求值。
因此,我们能够很容易求得这个表达式的结果:!10的结果是0,因为10是非О数;10&12的结果是1,因为10和12都不是O;10lI 12的结果也是1,因为10不是0。而且,在最后一个式子中,12根本不会被求值;
在表达式1+10 || 2+5中,2+5也不会被求值。
考虑下面的代码块,其作用是在数组arr中查询一个特定的元素:
int i =0;
int arr[10]={1,2,3,4,5,6,7,8,9,10};
int x = 11;
while(i<10 && arr[i] !=x)
{
i++;
}
这个循环语句的意思是:如果i等于10时循坏终止,就说明在数组arr中没有发现要找的元素;而如果是其他情况,此时i的值就是要找的元素在数组中。
假设我们无意中用运算符 按位与&替换了上面语句中的运算符逻辑与&&:
while(i<10 & arr[i] != x)
{
i++;
}
这个循坏语句也有可能“正常”工作,但仅仅是因为两个非常侥幸的原因。
**
- 第一个“侥幸”是:while中的表达式运算符&的两侧都是比较运算,而比较运算的结果为“真”时等于1,在为“假”时等于0。只要x和y的取值都限制在0或1,那么x&y 和 x&&y总是得出相同的结果。然而,如果两个比较运算中的任何一个用除1之外的非0数代表“真”,那么这个循坏就不能正常工作了。
- 第二个“侥幸”是:对于数组结尾之后的下一个元素(实际上是不存在的),只要程序不去改变该元素的值,而仅仅读取它的值,一般情况下是不会有什么危害的.运算符&和运算符&&不同,运算符&两侧的 *** 作数都必须被求值.所以在后一个代码段中,如果10等于arr[i]中的元素个数,当循环进入最后一次迭代时,即i等于10,也就是说数组元素arr[i]实际上并不存在,程序仍然会查看元素的值。
注意:对于数组越界访问后一个元素,取它的地址是合法的,但是不能越界访问数组开头前一个地址,这是违法的。这种做法的结果是未定义的,而且绝少有C编译器能检测出来这个错误(这种越界访问后果也就是本章开头给出的一例循坏)
本章暂且更新到这啦,后续会持续更新的喔。不说了…520祝各位单身狗快乐(还在这卷呢~)
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)