发件人: Chris Torek
主题:回复:define 中的 sizeof
日期:1999/11/26
新闻组:comp.lang.c
Message-ID: <81n9la$hhl$1@elf.bsdi.com>

C89 标准将翻译分为八个“阶段”。C99 保留了相同的阶段,并添加了 Unicode 等。我将在下面引用一份旧的 C99 草案(对文本表示进行了一些小的编辑)。请仔细阅读脚注 5,然后考虑“#if”发生在第 4 阶段,但 pp-tokens 直到第 7 阶段才转换为常规 token。由于“sizeof”关键字是常规 token 而不是 pp-token,这使得“#if”无法识别它。事实上,这一行

	# if ((sizeof aszColors / sizeof *aszColors) != COLOR_NB)

在没有各种#define的情况下,必须()的行为就好像它是这样读的

	# if ((0 0 / 0 *0) != 0)

因此需要诊断(因为“0 0”是语法错误)。

(当然,一旦发出了必要的诊断,编译器就可以随意执行任何操作,包括回溯并发现“0”来自“sizeof”,计算大小,检查枚举常量等等。但是,要使编译器做到这一点,其第 4 阶段的代码必须能够查看第 7 阶段的数据,例如枚举常量值和数组大小。这反过来需要一个集成的编译器——预处理不能像在 gcc 中那样作为一个独立程序进行。这反过来又使使用多个 CPU,例如通过“cpp | cc1”变得更加困难——因此保留这种排序以及脚注 5 可能是一个好主意。)

[以下摘录自 c9x.n2620.txt]

工作草案,1997-11-21,WG14/N794 J11/97-158
5.1.1.2 翻译阶段

[#1] 翻译的语法规则的优先级由以下阶段指定。[脚注 5]

1.
物理源文件中的多字节字符被映射到源字符集(必要时引入换行符作为行尾指示符)。源文件中的任何不在基本源字符集中的多字节字符都将被替换为指定该多字节字符的通用字符名。[6] 然后,三字母组序列被替换为相应的单字符内部表示。
2.
反斜杠字符后紧跟换行符的每个实例都被删除,将物理源行拼接起来形成逻辑源行。任何物理源行上的最后一个反斜杠才有资格成为这种拼接的一部分。非空源文件应以换行符结尾,在进行任何此类拼接之前,该换行符不应紧跟在反斜杠字符之前。
3.
源文件被分解为预处理 token[7] 和空白字符序列(包括注释)。源文件不应以不完整的预处理 token 或注释结尾。每个注释都将被一个空格替换。保留换行符。除了换行符之外的非空空白字符序列是否被保留或替换为一个空格由实现定义。
4.
预处理指令被执行,宏调用被展开,pragma 一元运算符表达式被执行。如果由 token 串联(6.8.3.3)生成的字符序列匹配通用字符名的语法,则行为是未定义的。一个#include预处理指令会递归地使指定的头文件或源文件经过从第 1 阶段到第 4 阶段的处理。然后删除所有预处理指令。
5.
字符常量和字符串字面量中的每个源字符集成员、转义序列和通用字符名都被转换为执行字符集成员。
6.
相邻的字符字符串字面量 token 被串联,相邻的宽字符串字面量 token 被串联。
7.
分隔 token 的空白字符不再重要。每个预处理 token 都被转换为一个 token。生成的 token 被进行语法和语义分析,并作为翻译单元进行翻译。
9.
所有外部对象和函数引用都得到解析。链接库组件以满足当前翻译中未定义的函数和对象的外部引用。所有此类翻译器输出被收集到一个程序映像中,该映像包含执行环境执行所需的信息。

__________[脚注 5 至 7]

5.
实现必须表现得好像这些独立的阶段发生一样,即使许多阶段在实践中通常被合并在一起。
6.
处理扩展字符的过程通过映射到只使用基本源字符集的编码来指定,并且在字符常量和字符串的情况下,进一步映射到执行字符集。然而,在实际操作中,可以使用任何内部编码,只要输入中遇到的实际扩展字符,以及在输入中表示为通用字符名(即使用\U\u表示法)的相同扩展字符,都被同等地处理。
7.
如 6.1 所述,将源文件字符划分为预处理 token 的过程是上下文相关的。例如,请参阅处理<#include预处理指令中。

--
现实生活中:Chris Torek,Berkeley Software Design Inc
El Cerrito, CA 域:torek@bsdi.com      +1 510 234 3167
http://claw.bsdi.com/torek/  (不一定在线)        我向 abuse@ 报告垃圾邮件。