【C语言必看】哟写BUG呢,我敢保证你踩过坑

【C语言必看】哟写BUG呢,我敢保证你踩过坑,第1张


文章目录
  • 陷进
    • 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编译器进行的测试


1.1 = 不同于 ==

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比较无效,但是作为程序员不应该指望靠编译器来提醒,而且并不是所有编译器都具备这样的功能。

√1.2 & 与 | 不同于 && 与 ||

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祝各位单身狗快乐(还在这卷呢~)

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/1295771.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-10
下一篇 2022-06-10

发表评论

登录后才能评论

评论列表(0条)

保存