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

Souleigh ✨ 等级 708 0 0

一 内容

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 & 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"); 

等价于:

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 缺陷与陷阱》


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

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

收藏
评论区

相关推荐

【C 陷阱与缺陷 学习笔记】(一)词法陷阱
一 内容 0\. 不同于 当程序员本意是作比较运算时,却可能无意中误写成了赋值运算。 1.本意是检查 x 与 y 是否相等: c if(x y) break; 实际上是将 y 的值赋值给了 x ,然后再检查该值是否为 0 。 2.本意是跳过文件中的空白字符: c while(c '' || c '\t' ||
C语言入门系列之2.数据类型、运算符和表达式
一、数据类型C语言常见数据类型如下: 1.数据类型 基本数据类型基本数据类型最主要的特点是,其值不可以再分解为其他类型。也可以说,基本数据类型是自我说明的。 构造数据类型构造数据类型是根据已定义的一个或多个数据类型用构造的方法来定义的。也就是说,一个构造类型的值可以分解成若干个“成员”或“元素”。每个“成员”都是一个基本数据类型或
C++学习建议
// 转载 C++学习建议 C++缺点之一,是相对许多语言复杂,而且难学难精。许多人说学习C语言只需一本K&R《C程序设计语言》即可,但C++书籍却是多不胜数。我是从C进入C++,皆是靠阅读自学。在此分享一点学习心得。个人认为,学习C++可分为4个层次: 第一层次:C++基础:挑选一本入门书籍,如《C++ Primer》、《C++大学教程》、或Stro
C# TCP与UDP
Http使用端口是80 SMTP使用端口是25 TCP是首选协议,它提供有保证的传输、错误校正和缓冲。 System.Net.Sockets. TcpClient类封装了TCP链接,提供属性字段来控制链接,包括缓冲、缓冲区的大小和超时。 TCP提供很多功能来确保数据的传输,它还提供了错误校正以及当数据丢失或数据包损坏时重新传输的功能。TCP可缓冲传入
C#与.NET
C#从本质上来说,它是一门编程语言。他不是.NET体系的一部分。虽然C#总是用来生成.NET平台的代码。C#语言不能孤立的使用,必须与.NET平台一起使用。这样才能大大提高开发效率。 相对于C++,Java等语言,C#是一种比较新的语言。它是在吸收了C++和Java语言在设计上的优点设计出来的。从一开始C#就被设计为面向对象的开发语言。 .NET Fra
GCC编译 C与C++ C89与C99
1) 最初的 ANSI C 标准 (X3.159-1989) 在 1989 年被批准,并于 1990 年发布。稍后这个标准被接受为 ISO 标准 (ISO/IEC 9899:1990) 。虽然 ISO 标准将 ANSI 标准的某些章节重新编号并变为条款,但是两者实际上并无区别。不论是 ANSI 标准还是 ISO 标准,通常都称之为 C89 ,偶尔也因为发布日
GNU C 与 ANSI C的区别
1.零长度数组 GNU C允许使用零长度数组,定义变长度对象时比较方便 struct var\_data {     int len;     char data\[0\]; }; var\_data的大小仅为一个int型,data是常量地址,data\[index\]是访问其后的内存空间。 struct var\_data \*s = mal
@protocol (协议)和 @interface (接口)的区别
Objective-C 中的协议(@protocol),相当于 C#, Java 等语言中的接口 (Interface)。协议本身不实现任何方法,只是声明方法,使用协议的类必须实现协议方法。 Objective-C 中的接口(@interface),相当于 C#, Java 等语言中的类(Class),是类的一个声明,不同与 C#, Java 等语言的接口
Java与c#的一些细节区别
**实习中用的语言是c#,第一次接触到这种语言,然后写的过程中,发觉和Java几乎一摸一样,好像根本是无缝切换,但细节仍有很大的区别,称有空总结一波里面的部分细节实现。** **ps. 我写c#过程中,发觉c#有很多优秀的特性,写起来在方便很多,比如lambda,linq等** 1.Lambda VS Delegate =================
ROS与C++
* 构建工作空间 `**catkin_make**` * 构建Catkin包 `**catkin_create_pkg**` # This is an example, do not try to run this # catkin_create_pkg <package_name> [depend1] [depend2] [dep
ANSI C、ISO C、Standard C联系与区别
做C语言开发的人,经常会遇到“ANSI C”、“ISO C”与“Standard C”三种术语,经常会让人傻傻分不清楚。博主之前按也是搞不清三者的关系,于是某天下定决心,一定要搞清楚三者的关系,先百度上搜下。下面是在百度百科上搜索关键字“ANSI C”得到的结果: \================================分割线==
Android C、C++与java端3DES互通
<div id="article\_content" class="article\_content clearfix"> <div class="article-copyright"> <span class="creativecommons"> <a rel="license" href="http://creativecommons.org/licen
Emacs常用快捷键
快捷键 --- M系列组合键:操作由语言定义的单位(如此、句子、段落等) C系列组合键:操作与语言无关的基本单位(如字符、行等) 移动到下一屏:C-v 移动到上一屏:M-v 光标所在行移动到屏幕中间:C-l 上一行:C-p 下一行:C-n 向左移:C-b 向右移:C-f 向前移动一个词:M-f 向后移动一个词:M-b 移动到行首:C-
Lua学习总结
简述 ===== Lua是一个很小的编程语言,很多人将其与Python高级语言进行比较。 Lua有以下的特点: (1)纯C语言实现,源码小,可以很好地与C/C++融合。可自行编译,生成静态库。 (2)语法简单,灵活,易学。 我也同时学习了Python,相比之下,Lua精简,功能简单,可用的库少,但是语言的实现写得如此精简也很不错了!与C
Thinking in java Chapter15 泛型
* 1 与C ++比较 * 2 简单泛型 泛型 类 * 3 泛型 接口 * 4 泛型 方法 * 5 匿名内部类 * 6 构建复杂模型 * 7 * 8 * 9 * 10 “泛型”意思就是:适用于许多许多的类型 <h2 id="1">1 与C++比较 </h2> ------------------------- C