跳过导航链接 | |
退出打印视图 | |
![]() |
Oracle Solaris Studio 12.3:C 用户指南 Oracle Solaris Studio 12.3 Information Library (简体中文) |
这可能是以前的 C 版本中最少涉及的部分,涉及将每个源文件从一串字符转换为标记序列(即可进行解析)的操作。这些操作包括白空间(包括注释)的识别、将连续指令捆绑为标记、处理预处理指令行以及宏替换。然而,从未对其各自顺序提供保证。
这些转换阶段的顺序由 ISO C 指定。
替换源文件中的每个三字符序列。ISO C 恰好具有九个三字符序列,以一种字符集不足的特许形式单独创建。这些三字符的序列用于命名不在 ISO 646-1983 字符集中的字符:
表 6-1 三字符序列
|
ISO C 编译器必须理解这些序列,但建议不要使用它们。在您使用 -xtransition 选项时,只要 ISO C 编译器在转换 (-Xt) 模式下替换三字符(甚至是在注释中),它就会向您发出警告。例如,考虑以下情形:
/* comment *??/ /* still comment? */
??/ 变为反斜杠。该字符和后面的换行符被删除。结果字符为:
/* comment */* still comment? */
第二行的第一个 / 是注释的结尾。下一个标记是 *。
删除每个反斜杠/换行符对。
源文件转换为预处理标记和白空间序列。每个注释有效地替换为一个空格字符。
处理各个预处理指令并替换所有宏调用。每个 #included 源文件在其内容替换指令行之前运行较早的阶段。
解释各个换码序列(形式为字符常量和文本字符串)。
串联相邻文本字符串。
每个预处理标记都将转换为一个常规标记。编译器将正确解析这些标记并生成代码。
解析所有外部对象和函数引用,形成最终程序。
以前的 C 编译器不执行如此简单的阶段序列,并且应用这些步骤的顺序是不可预测的。独立预处理程序识别标记和空白的时间基本上与它替换宏和处理指令行的时间相同。然后输出由适当的编译器完全重新标记化,接着编译器解析语言并生成代码。
预处理程序中的标记化过程是经常性操作,宏替换以基于字符的操作而不是基于标记的操作形式完成。因此,标记和空格的作用在预处理期间会有很大不同。
这两种方法存在很多差异。本节其余部分讨论代码行为如何因宏替换过程中发生的行拼接、宏替换、字符串化以及标记粘贴而更改。
在 K&R C 中,仅允许将反斜杠/换行符对作为一种将指令、文本字符串或字符常量延续到下一行的方法。ISO C 扩展了该概念以便反斜杠/换行符对可以将任何内容延续到下一行。结果为逻辑源代码行。因此,依赖于反斜杠/换行符对任一侧上单独标记识别的任何代码的行为不像预期的那样。
ISO C 之前的版本中未详细描述宏替换过程。这种不明确性产生了许多有歧义的实现。依赖于比明显常量替换和简单类函数宏更复杂的事情的任何代码可能并不真正可移植。本手册无法指出旧 C 宏替换实现与 ISO C 版本之间的所有差异。除标记粘贴和字符串化之外的几乎所有宏替换的使用产生的标记系列均与以前完全相同。此外,ISO C 宏替换算法可以完成在旧 C 版本中无法完成的工作。以下示例使 name 的任何使用均替换为通过 name 进行的间接引用。
#define name (*name)
旧 C 预处理程序会产生大量圆括号和星号,并最终产生关于宏递归的错误。
ISO 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)
此示例生成以下结果,该结果对 ASCII L 控制字符求值。
(037 & ’L’)
最佳解决办法是将此宏的所有用法更改为如下形式:
#define CNTL(ch) (037 & (ch)) CNTL(’L’)
此代码的可读性和实用性更强,因为它还可以应用于表达式。
K&R C 至少有两种方法将两个标记组合在一起。以下代码中的两个调用均使用 x 和 1 两个标记生成单个标识符 x1。
#define self(a) a #define glue(a,b) a/**/b ? self(x)1 glue(x,1)
同样,ISO C 不认可这两种方法。在 ISO C 中,这两个调用均生成两个独立标记 x 和 1。可以通过使用 ## 宏替换运算符针对 ISO C 重新编写二种方法中的第二种:
#define glue(a,b) a ## b glue(x, 1)
只有在定义了 __STDC__ 时,才应将 # 和 ## 用作宏替换运算符。由于 ## 是实际运算符,因此对于定义和调用中的空白,调用更加自由。
编译器针对未定义的 ## 运算(C 标准,第 3.4.3 节)(其中一个 ## 结果未经定义,当进行预处理时,包含多个标记而不是一个标记(C 标准,第 6.10.3.3(3) 节))发出警告诊断。未定义的 ## 运算的结果现在定义为通过预处理连接 ## 操作数所创建的字符串而生成的第一个独立标记。
没有什么直接方式可用来生成两种旧式粘贴方案中的第一种方案,但是由于它在调用时引入了粘贴的任务,因此使用它的频率比使用其他形式要低。