01 词法分析陷阱:C编程中的符号误解
编译器中负责将程序分解为一个一个符号的部分,一般称为“词法分析器”。
本章将探讨:
- 符号和组成符号的字符间的关系
- 有关符号含义的一些常见误解。
1. =
不同于==
一般而言,赋值运算相对于比较运算出现得更频繁,因此字符数较少的符号=就被赋予了更常用的含义—赋值操作。
这样带来了一些好处: 出现频率更高的赋值运算符更加容易的被书写
但是也带来了一个潜在问题: 程序员本意是作比较运算,却可能无意中误写成了赋值运算。
1.1. if (x = y)
if (x = y) break;
带来了一种结果: 只要y不是0, 那么if永远为真.
1.2. while (c = ' ' || c == '\t' || c == '\n')
while (c = ' ' || c == '\t' || c == '\n') // 等价于: c = (' ' || c == '\t' || c == '\n') c = getc (f);
等价于: c = (' ' || c == '\t' || c == '\n')
换言之, while 恒为真 => 死循环
2. &
和 |
不同于&&
和 ||
&
: 按位与|
: 按位或&&
: 逻辑与||
: 逻辑或
3. 词法分析中的 "贪心法"
C中会存在由多个字符组成的符号, 因此词法分析的时候每读一个字符, 就需要判断是否与前面的字符共同构成 一个符号?
C编译器词法分析器采用了最简单的方式 -- 如果当前字符与前面字符能够构成一个有效符号, 那就构成, 否则便不共同构成一个符号. 换言之: 每一个符号应该包含尽可能多的字符.
这种词法分析原则本身是没问题的, 但是架不住有些程序员代码书写习惯不良造成代码的含义出现问题.
3.1. a---b
a---b
含义是: a-- - b
3.2. y = x/*p /* p指向除数*/;
y = x/*p /* p指向除数*/;
程序员 原意 可能为: y = x / (*p)
但是实际上为: y = x
4. 整型常量
如果一个整型常量的第一个字符是数字0,那么该常量将被视作八进制数。
4.1. 10 != 010
int a = 10;
int b = 010;
a的值是10
b的值被当作8进制数解释, 值为8
4.2. 8和9也算八进制数?
有些编译器允许8和9也可以当作八进制数字处理
int a = 0195;
0195的含义是1×8^2+9×8^1+5×8^0,也就是141(十进制)或者0215(八进制)。
我们当然不建议这种用法,ANSI C标准也禁止这种用法。
4.3. 格式对齐可能造成的数字错误
有些时候, 为了格式对齐, 可能无意写成了八进制数字.
struct {int part_number;char *description;
}parttab[] = {046, "left-handed widget" ,047, "right-handed widget" ,125, "frammis"
};
5. 字符 和 字符串
C中的''
和""
含义完全不同:
''
: 代表一个字符常量, 本质是数字,
eg: 'a'与97等价""
: 代表的是一个字符串常量, 其本质是一个指向无名数组的指针, 被指向的无名数组由若干个字符和\0
组成.
eg:printf ("Hello world\n");
等价于char hello[] = {'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '\n', 0}; printf (hello);
5.1. printf('\n');
某些C编译器对函数参数并不进行类型检查,特别是printf函数的参数。
printf('\n'); // error
替代
printf("\n"); // right
可能会出现难以预料的结果.
5.2. 'abcd'
前面说过, 'c'本质是一个整数, 默认是int类型.
整型数(一般为16位或32位)的存储空间可以容纳多个字符(字符一般为8位),因此有的C编译器允许在一个字符常量(以及字符串常量)中包括多个字符.
初学者很容易把
"abcd";
写成:
'abcd'
如果写错了, 结果却恰好符合预期的, 那么完全是你运气好, 因为标准中没有规定应该是什么行为, 这高度依赖编译器的实现.
6. 练习题
6.1. 嵌套注释
某些C编译器允许嵌套注释。请写一个测试程序,要求无论是对允许嵌套注释的编译器,还是对不允许嵌套注释的编译器,该程序都能正常通过编译(无错误消息出现),但是这两种情况下程序执行的结果却不相同。
#include <stdio.h>int main() {int x = 0;/* 外层注释开始x = 1;/* 内层注释 */x = 2;//*/ x = 3;printf("x = %d\n", x);return 0;
}
如果由你来实现一个C编译器,你是否会允许嵌套注释?如果你使用的C编译器允许嵌套注释,你会用到编译器的这一特性吗?你对第二个问题的回答是否会影响到你对第一个问题的回答?
答案:
- 不允许嵌套注释
- 不会用到
- 不会
6.2. 大嘴词法分析器
为什么n-->0的含义是n-- > 0,而不是n- ->0?
答: 词法分析器的"贪心"解析
a+++++b的含义是什么?
a++ + ++b;