【C 陷阱与缺陷 学习笔记】(一)词法陷阱

一 内容

0. =不同于==

当程序员本意是作比较运算时,却可能无意中误写成了赋值运算。

1.本意是检查 x 与 y 是否相等:

1if(x = y) 2 break;

实际上是将 y 的值赋值给了 x ,然后再检查该值是否为 0 。

2.本意是跳过文件中的空白字符:

1while(c = '' || c == '\t' || c == '\n') 2 c = getc(f);

因为 ' '不等于 0 (' '的 ASCII 码值为 32),那么无论变量为何值,上述表达式求值的结果都为 1,因此循环将进行下去直到整个文件结束。

C 编译器发现形如 x = y 的表达式出现在选择语句,循环语句的条件判断部分时,会给出警告。当确实需要对变量进行赋值时,为了避免警告,我们应该这样处理:

1if((x = y) != 0) 2 foo();

如果将赋值写成了比较,也会造成混淆:

1if((filedesc == open(argv[i], 0)) < 0) 2 error();

本例中,open 执行成功返回非零值,失败返回 -1。本意是将 open 函数的返回值存储在变量 filedesc 中,然后将其和 0 比较大小,判断 open 执行是否成功 。==运算符的结果只可能是 1 或 0,永远不会小于 0,所以 error() 将没有机会被调用。

1. &|不同于&&||

比较 i & ji && 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"); 

等价于:

1char hello[] = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '\n', 0}; 2printf(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 缺陷与陷阱》


以上就是本次的内容,感谢观看。

如果文章有错误欢迎指正和补充,感谢!

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

Opencv中Mat矩阵相乘——点乘、dot、mul运算详解

Opencv中Mat矩阵相乘——点乘、dot、mul运算详解2016年09月02日00:00:36 \牧野(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fme.csdn.net%2Fdcrmg) 阅读数:59593