Sun Studio 12:C 用户指南

6.5 标记化和预处理

这可能是以前的 C 版本中最少涉及的部分,涉及将每个源文件从一串字符转换为标记序列(即可进行语法分析)的操作。这些操作包括白空间(包括注释)的识别、将连续指令捆绑为标记、处理预处理指令行以及宏替换。然而,从未对其各自顺序提供保证。

6.5.1 ISO C 转换阶段

这些转换阶段的顺序由 ISO C 指定。

替换源文件中的每个三字符序列。ISO C 正好有九个为弥补不完善的字符集而单独构造的三字符序列,它们是命名 ISO 646-1983 字符集之外的字符的三字符序列:

表 6–1 三字符序列

三字符序列 

转换为 

??=

#

??-

~

??(

[

??)

]

??!

|

??<

{

??>

}

??/

\

??’

^

ISO C 编译器必定理解这些序列,但我们建议不要使用它们。在您使用 -xtransition 选项时,只要 ISO C 编译器在转换 (–Xt) 模式下替换三字母(甚至是在注释中),它就会向您发出警告。例如,考虑以下情形:


/* comment *??/
/* still comment? */

??/ 变为反斜杠。该字符和后面的换行符被删除。结果字符为:


/* comment */* still comment? */

第二行的第一个 / 是注释的结尾。下一个标记是 *

  1. 删除每个反斜杠/换行符对。

  2. 源文件转换为预处理标记和白空间序列。每个注释有效地替换为一个空格字符。

  3. 处理各个预处理指令并替换所有宏调用。每个 #included 源文件在其内容替换指令行之前运行较早的阶段。

  4. 解释各个换码序列(形式为字符常量和字符串文字)。

  5. 并置相邻字符串文字。

  6. 各个预处理标记转换为常规标记,编译器正确分析这些标记并生成代码。

  7. 解析所有外部对象和函数引用,形成最终程序。

6.5.2 旧 C 转换阶段

以前的 C 编译器不执行如此简单的阶段序列,也不保证何时应用这些步骤。独立预处理程序识别标记和空白的时间基本上与它替换宏和处理指令行的时间相同。然后输出由适当的编译器完全重新标记化,接着编辑器分析语言并生成代码。

由于预处理程序中标记化进程的操作时间很短,且宏替换是作为基于字符(而不是基于标记)的操作执行的,因此在预处理过程中标记和空白可能会发生很大的变化。

这两种方法存在很多差异。本节其余部分讨论代码行为如何因宏替换过程中发生的行拼接、宏替换、字符串化以及标记粘贴而更改。

6.5.3 逻辑源代码行

在 K&R C 中,仅允许将反斜杠/换行符对作为一种将指令、字符串文字或字符常量延续到下一行的方法。ISO C 扩展了该概念以便反斜杠/换行符对可以将任何内容延续到下一行。结果为逻辑源代码行。因此,依赖于反斜杠/换行符对任一侧上单独标记识别的任何代码的行为不像预期的那样。

6.5.4 宏替换

在 ISO C 之前,从未详细描述宏替换过程。这种不明确性产生很多有分歧的实现。依赖于比明显常量替换和简单类函数宏更奇特的事情的任何代码可能并不真正可移植。本手册无法指出旧 C 宏替换实现与 ISO C 版本之间的所有差异。除标记粘贴和字符串化之外的几乎所有宏替换的使用产生的标记系列均与以前完全相同。此外,ISO C 宏替换算法可以完成在旧 C 版本中无法完成的工作。例如,


#define name (*name)

使 name 的任何使用均替换为通过 name 进行的间接引用。旧 C 预处理程序会产生大量圆括号和星号,并最终产生关于宏递归的错误。

ISO C 对宏替换方法的主要更改是:要求在替换标记列表中进行替换之前针对宏参数(而不是那些本身是宏替换操作符 ### 的操作数)进行递归扩展。然而,这种更改很少在结果标记中产生实际差异。

6.5.5 使用字符串


注 –

在 ISO C 中,如果您使用 -xtransition 选项,则以下带有 ? 标记的示例将生成警告。仅在转换模式(–Xt-Xs)下,结果才与以前版本的 C 相同。


在 K&R C 中,以下代码生成字符串文字 "x y!"


#define str(a) "a!"   ?
str(x y)

因此,预处理程序在字符串文字和字符常量中查找看起来类似宏参数的字符。ISO C 认识到此功能的重要性,但不允许对部分标记的操作。在 ISO C 中,以上宏的所有调用均生成字符串文字 "a!"。为在 ISO C 中实现旧效果,我们使用 # 宏替换操作符和字符串文字并置。


#define str(a) #a "!"
str(x y)

以上代码生成两个字符串文字 "x y""!",它们在并置后生成相同的 "x y!"

不直接替换字符常量的类似操作。此功能的主要用法与以下类似:


#define CNTL(ch) (037 & ’ch’)    ?
CNTL(L)

它生成


(037 & ’L’)

求值为 ASCII control-L 字符。我们知道的最佳解决办法是将此宏的用法更改为:


#define CNTL(ch) (037 & (ch))
CNTL(’L’)

此代码的可读性和实用性更强,因为它还可以应用于表达式。

6.5.6 标记粘贴

在 K&R C 中,将两个标记组合在一起至少有两种方法。以下代码中的两个调用均使用 x1 两个标记生成单个标识符 x1


#define self(a) a
#define glue(a,b) a/**/b ?
self(x)1
glue(x,1)

同样,ISO C 不认可这两种方法。在 ISO C 中,以上两个调用均生成两个独立标记 x1。可以通过使用 ## 宏替换操作符针对 ISO C 重新编写以上第二种方法:


#define glue(a,b) a ## b
glue(x, 1)

只有在定义了 __STDC__ 时,才应将 # 和 ## 用作宏替换操作符。由于 ## 是实际操作符,因此对于定义和调用中的空白,调用更加自由。

没有什么直接方式可用来实现两种旧式粘贴方案中第一种方案,但是由于它在调用时引入了粘贴的任务,因此使用它的频率比使用其他形式要低。