C 预处理器文本转换过程

C 预处理器(CPP)是 C 编译器自带的在编译之前执行的宏处理器(macro processor). 当遇到类似于 #define 后边带有行注释时不免会疑惑, 这个行注释会不会影响到替换后的代码行. 带着这个问题查阅了一下 CPP 的文档, 并找到了相应的解释.

在 CPP 执行宏处理之前, 会执行一系列的文本转换(textual transformation), 这些文本转换发生在所有的其它处理之前, 并且严格按照顺序执行.

首先将输入文件读入内存并且按照行划分, GCC 接收 LF, CR LF 和 CR 作为行结束标记( end-of-line marker ), 它们分别用于 Unix, DOS 和 Mac OS (后来 Mac OSX 选用了和 Unix 一样的行结束标记). 所以在多个平台上拷贝编译源文件是不会出错的, 但是需要注意的是如果同一项目中的多个源文件之间的行结束符不一致会导致 GCC 无法跟踪当前行号.

第二步是将三字母词(trigrph)替换为相应的单字符, 三字母词就是几个字符的序列, 合起来表示另一个字符. 三字母词用于在很老的系统上, 这些系统缺乏 C 中的一些必要的标点符号. 总共有 9 个三字母词:

Trigraph:    ??(  ??)  ??<   ??>  ??=  ??/  ??’  ??!  ??-
Replacement:  [    ]    {     }    #    \    ^    |    ~

GCC 中默认是关闭三字母词的转换的, 可以使用 -trigraphs 开启, 但是三字母词是一个不推荐的做法, 因为它既不常见而且在某些平台上实现的也有问题.

第三步是将连续行(continued line)合并. 连续行是以反斜线()结束的行. 最终反斜线会被移除, 然后将紧接着一行合并到当前行. 这种反斜线紧接着换行符可以在任意位置, 甚至可以在一个单词的中间, 但是这不是推荐的做法. 推荐的做法是将反斜线和换行符放在空白符中. 这种反斜线和换行符的组合有一个专有名称为反斜线-换行符(backslash-newline) . 如果在反斜线和换行符之间包含了空白符可能会产生错误或者警告.

第四步是将所有注释替换为单个空格. 这样即便在 #define 后边有一个单行注释或块注释都不会对宏处理之后的源代码产生影响. 下面例子来自于 CPP 文档:

/\
*
*/ # /*
*/ defi\
ne FO\
O 10\
20 //This is comment

/* 等价于 */

#define FOO 1020

上面所有注释在文本转换处理过程中都会消失, 当然这种写法也不是推荐的写法, 因而需要少用.

至此在宏处理之前的整个文本转化工作就做完了. 从中也可以明白宏处理是不会带上注释的, 可以放心的在 #define 后边加上注释了.

Leave a Reply

Your email address will not be published. Required fields are marked *