【C 陷阱与缺陷 学习笔记】(一)词法陷阱
码字不易,对你有帮助 点赞/转发/关注 支持一下作者
微信搜公众号:不会编程的程序圆
看更多干货,获取第一时间更新
一 内容
0. =
不同于==
当程序员本意是作比较运算时,却可能无意中误写成了赋值运算。
1.本意是检查 x 与 y 是否相等:
if(x = y)
break;
实际上是将 y 的值赋值给了 x ,然后再检查该值是否为 0 。
2.本意是跳过文件中的空白字符:
while(c = '' || c == '\t' || c == '\n')
c = getc(f);
因为 ' '
不等于 0 (' '
的 ASCII 码值为 32),那么无论变量为何值,上述表达式求值的结果都为 1,因此循环将进行下去直到整个文件结束。
C 编译器发现形如 x = y 的表达式出现在选择语句,循环语句的条件判断部分时,会给出警告。当确实需要对变量进行赋值时,为了避免警告,我们应该这样处理:
if((x = y) != 0)
foo();
如果将赋值写成了比较,也会造成混淆:
if((filedesc == open(argv[i], 0)) < 0)
error();
本例中,open 执行成功返回非零值,失败返回 -1。本意是将 open 函数的返回值存储在变量 filedesc 中,然后将其和 0 比较大小,判断 open 执行是否成功 。==
运算符的结果只可能是 1 或 0,永远不会小于 0,所以 error() 将没有机会被调用。
1. &
和|
不同于&&
和||
比较 i & j
和 i && j
,只要 i 和 j 是 0 或 1 ,两个表达式的值是一样的(|
和 ||
同理。)。然而,一旦 i 和 j 的值为其他,两个表达式的值不会始终一致。
另一个区别是操作数带有自增自减的运算:
i & j++
, j 始终会自增;但是 i && j++
有时 j 不会自增。
2. 词法分析中的“贪心法”
当 C 的编译器读入一个字符/
后跟着一个字符*
时,那么编译器就必须做出判断:时将其作为两个符号对待,还是合起来作为一个符号对待。这类问题的规则:每个符号应该包含尽可能多的符号。
例如:a---b
和(a--) - b
含义相同,而与a - (--b)
含义不同。
又如:下面的语句本意是 x 除以 p 指向的值然后将结果赋值给 y
y = x/*p;
但是,实际上 /*
被编译器理解为一段注释的开始。
将上面的语句重写如下:
y = x / *p;
或者:
y = x/(*p);
老版本的编译器允许使用=+
来代表现在+=
的含义,这种编译器会将:
a=-1;
理解为:
a =- 1;
即为:
a = a - 1;
因此,如果程序员的原意为:
a = -1;
那么结果会让其大吃一惊。
再如:
a=/*b;
在老版本的编译器会将其当作:
a =/ *b;
3. 整型常量
许多编译器会把 8 和 9 作为把八进制的数字处理,这种处理方式来源于八进制数的定义。例如:0195 的含义是1x8^2 + 9x8 + 5x8^0
也就是 141(十进制)或 0215(八进制)。ANSI C 标准中禁止这种用法。
4. 字符与字符串
单引号引起的一个字符实际上代表一个整数。整数值对应于该字符在编译器采用的字符集中的序列值。因此,对于采用 ASCII 字符集的编译器而言,'a'
的含义与 97 (十进制)严格一致。
用双引号引起的字符串,代表的确实一个指向无名数组起始字符的指针。该数组被双引号之间的字符以及一个额外的二进制值为 0 的字符\0
初始化。
比如,下面的这个语句:
printf("Hello World\n");
等价于:
char hello[] = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '\n', 0};
printf(hello);
整数型(一般为 16 或 32 位)的存储空间可以容纳多个字符(一般为 8 位),因此有的编译器允许在一个字符常量(以及字符串常量)中包含多个字符。也就是说:用'yes'
代替"yes"
不会被该编译器检测到。前者的含义大多数编译器理解为一个整数值,由'y','e','s'
所代表的整数值按照特定编译器实现中的定义方式组合得到。
二 练习
练习 1
某些 C 编译器允许嵌套注释。请写一个测试程序,要求:无论编译器是否允许嵌套注释,该程序都能正常通过编译,但是两种情况下程序执行结果不同。
对于符号序列:
/*/**/"*/"
如果允许嵌套注释,上面的符号序列表示:一个单独的双引号"
,因为最后的注释符前出现的符号都会被当作注释的一部分。
如果不允许嵌套注释,上面的符号就表示一个字符串:"*/"
Doug Mcllroy 发现了下面这个令人拍案叫绝的解法:
/*/*/0 */**/1
这个解法主要利用了编译器作词发分析时的“贪心法”规则。
如果编译器允许嵌套注释,则将上式解释为:
/* /*/0 */ * */ 1
上式的值为 1
如果编译器不允许嵌套注释,则解释为:
/* / */ 0 * /**/ 1
也就是 0*1
,值为 0
练习 2
a+++++b
的含义是什么?
上式唯一有意义的解析方式就是:
a++ + ++b
可是,根据“贪心法”的规则,上式应该被解释为:
a++ ++ + b
等价于:
(a++)++ + b;
但是 a++
的值不能作为左值,因此编译器不会接受 a++ 作为后面 ++ 运算的操作数。
参考资料:《C 缺陷与陷阱》
以上就是本次的内容,感谢观看。
如果文章有错误欢迎指正和补充,感谢!
最后,如果你还有什么问题或者想知道到的,可以在评论区告诉我呦,我在后面的文章可以加上。
最后,关注我,看更多干货!
我是程序圆,我们下次再见。