C语言参考手册
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
版权说明:
本资料内容摘录自《C程序设计语言(第二版)》K&R著 徐宝文 李志译 尤晋元审校 机械工业出版社出版 一书。
版权属原作者和出版社所有。
制作本资料为了我本人学习和参考,非商业用途。
建议读者阅读原书学习比较好,它更详细。
目录:
A.12 预处理
主要介绍预处理器的功能和预处理的过程。
A.12.1三字符序列
主要介绍 ??=, ??(, ??<等三字符序列。
A.12.2 行连接
主要介绍反斜杠\结束的指令行处理过程。
A.12.3 宏定义和扩展
主要介绍#define 标识符 记号序列,#define 标识符(标识符表opt) 记号序列,#undef 标识符,还有#和##等一些东西,有一些例子。
A.12.4 文件包含
主要介绍#include <文件名>和#include “文件名”指令。
A.12.5 条件编译
介绍#if 常量表达式/#ifdef 标识符/#ifndef 标识符,#elif 常量表达式,#else,
#endif语句。
A.12.6 行控制
介绍#line指令。
A.12.7 错误信息生成
介绍#error指令。
A.12.8 pragma
介绍#pragma。
A.12.9 空指令
介绍空指令#。
A.12.10 预定义名字
介绍__LINE__, __FILE__, __DATE__, __TIME__, __STDC__等。
A.12 预处理 返回目录
预处理器执行宏替换,条件编译以及包含指定的文件。
以#开头的命令行(“#”前可以有空格)就是预处理器处理的对象。
这些命令行的语法独立于语言的其它部分,他们可以出现在任何地方,其作用可以延续到所在编译单元的末尾(与作用域无关)。
行边界是有实际意义的;每一行都将单独进行分析(有关如何将行连接起来的详细信息参考A.12.2节)。
对预处理器而言,记号可以是任何语言记号,也可以是类似于#include指令(参见A.12.4节)中表示文件名的字符序列。
此外,所有未进行其它定义的字符都将被认为是记号。
但是,在预处理器指令行中,除空格,横向制表符外的其它空白字符的作用是没有定义的。
预处理过程在逻辑上可以划分为几个连续的阶段(在某些特殊的实现中可以缩减)。
1) 首先,将A.12.1节所述的三字符序列替换为等价字符。
如果操作系统环境需要,还要在原文件的各行
之间插入换行符。
2) 将指令行中位于换行符前的反斜杠符\删掉,以把各指令行连接起来(参见A.12.2节)。
3) 程序分成用空白符分割的记号。
注释将被替换为一个空白符。
接着执行预处理指令,并且进行宏扩展
(参见A.12.3节~A.12.10节)。
4) 将字符常量和字符串字面值中的转义字符序列替换为等价字符,然后把相邻的字符串字面值连接起来。
5) 收集必要的程序和数据,并将外部函数和对象的引用与其定义连接,翻译经过以上处理的结果,然后与
其它程序和库连接起来。
A.12.1 三字符序列 返回目录
C语言源程序的字符集是7位ASCII码的子集,但它是ISO 646-1983不变代码集的超集。
为了将程序通过这种缩减的字符集表示出来,下列所示的所有三字符序列都要用相应的单个字符替换,这种替换在进行所有其它处理之前进行。
??= # ??( [ ??< {
??/ \ ??) ] ??> }
??’ ^ ??! | ??- ~
除此之外不进行其它替换。
说明:三字符序列是ANSI标准新引入的特征。
A.12.2 行连接
通过将以反斜杠\结束的指令行末尾的反斜杠和其后的换行符删除掉,可以将若干指令行合并一行。
这种处理要在分隔记号之前进行。
A.12.3 宏定义和扩展 返回目录
类似于下列形式的控制指令:
#define 标识符 记号序列
将使得预处理器把该标识符后续出现的各个实例用给定的记号序列替换。
记号序列前后的空白符将被丢弃掉。
第二次用#define指令定义同一个标识符是错误的,除非第二次定义的标记序列与第一次相同(所有的空白分隔符将被认为是相同的)。
类似于下列形式的指令行:
#define 标识符(标识符表opt) 记号序列
是一个带有形式参数(由标识符表指定)的宏定义,其中第一个标识符与圆括号(之间没有空格。
同第一种形式一样,记号序列前后的空白符都将被丢掉。
如果要对宏进行重新定义,则必须保证其形式参数个数、拼写及记号序列都必须与前面的定义相同。
类似于下列的控制指令:
#undef 标识符
用于取消标识符的预处理器定义。
将#undef应用于未知标识符(即未用#define指令定义的标识符)并不会导致错误。
按照第二种形式定义宏时,宏标识符(后面可以跟一个空白字符,空白符是可选的)及其后用一对圆括号括起来的、由逗号分隔的记号序列就构成了一个宏调用。
宏调用的实际参数使用逗号分隔的记号序列,用引号或嵌套的括号括起来的逗号不能用于分隔实际参数。
在处理过程中,实际参数不能进行宏扩展。
宏调用时,实际参数的数目必须与定义中的形式参数的数目匹配。
实际参数被分离后,前导和尾部的空白符被删除。
随后,由各实际参数产生的记号序列将替换未用引号引起来的相应形式参数的标识符(位于宏的替换记号序列中)。
除非替换序列中形式参数的前面有一个#符号,或者其前面或后面有一个##符号,否这,在插入前要对宏的实际参数记号进行检查,并在必要时进行扩展。
两个特殊的运算符会影响替换过程。
首先,如果替换记号序列中的某个形式参数前面直接是一个#符号(它们之间没有空白符),相应形式参数的两边将被加上双引号(″),随后,#和形式参数标识符将被用引号引起来的实际参数替换。
实际参数中的字符串字面值、字符常量两边或内部的每个双引号(″)或反斜杠(\)前面都要插入一个反斜杠(\).
其次,无论哪种宏的定义记号序列中包含一个##运算符,在形式参数替换后都要把##及前后的空白符都删除掉,以便将相邻记号连接起来形成一个新记号。
如果这样产生的记号无效,或者结果依赖于##与算符的处理顺序,则结果没有定义。
同时,##也可以不出现在替换记号序列的开头和结尾。
对这两种类型的宏,都要重复扫描替换记号序列以查找更多的已定义标识符。
但是,当某个标识符在某个扩展中被替换后,再次扫描并再次遇到此标识符是不再对其执行替换,而是保持不变。
即使执行宏扩展后得到的最终结果以#打头,也不认为它是预处理指令。
说明:有关宏扩展处理的细节信息,ANSI标准比第一版描述得更详细。
更重要的变化是加入了#和##与算符,这就使得引用和连接成为可能。
某些新规则(特别是与连接有关的规则)比较独特(参见下面的例子)。
例如,这种功能可以用来定义“表示常量”,如下例所示:
#define TABSIZE 100
int table[TABSIZE];
定义
#define ABSDIFF(a, b) ((a)>(b) ? (a)-(b) : (b)-(a))
定义了一个宏,它返回两个参数之差的绝对值。
与执行相同功能的函数所不同的是,参数与返回值可以是任意算术类型,甚至可以是指针。
同时,参数可能有副作用,而且需要计算两次,一次进行测试,一次则生成值。
假定有下列定义:
#define tempfile(dir) #dir ″/%s″
宏调用tempfile(/usr/tmp)将生成
″/usr/tmp″″/%s″
随后,该结果将被连接为一个单个的字符串。
给定下列定义:
#define cat(x, y) x ## y
那么,宏调用cat(var, 123)将生成var 123。
但是,宏调用cat(cat(1,2),3)没有定义:##阻止了外层调用的参数的扩展。
因此,他将生成下列记号串:
cat( 1 , 2 )3
并且,)3不是一个合法的记号,他由第一个参数的最后一个记号与第二个参数的第一个记号连接而成。
如果在引入第二层的宏定义,如下所示:
#define xcat(x,y) cat(x,y)
我们就可以得到正确结果。
xcat(xcat(1,2),3)将生成123,因为xcat自身的扩张不包含##运算符。
类似地,ABSDIFF(ABSDIFF(a,b),c)将生成所期望的经完全扩展后的结果。
A.12.4 文件包含 返回目录
下列形式的控制指令:
#include <文件名>
将把该行替换为文件名指定的文件的内容。
文件名不包含>或换行符。
如果文件名中包含字符″、‘、\或/*,则其行为没有定义。
预处理器将在某些特定的位置查找指定的文件,查找的位置与具体的实现相关。
类似地,下列形式的控制指令:
#include “文件名”
首先从源文件的位置开始搜索指定文件(搜索过程与具体的实现相关),如果没有找到指定的文件,则按照第一种定义的方式处理。
如果文件名中包含字符″、’、\或/*,其结果仍然是没有定义的,但是可以使用字符〉。
最后,下列形式的指令行:
#include 记号序列
同上述两种情况都不同,它将按照扩展普通文本的方式扩展记号序列进行解释。
记号序列必须被解释为<…>或″…″两种形式之一,然后再按照上述方式进行相应的处理。
#include 文件可以嵌套。
A.12.5 条件编译 返回目录
对一个程序的某些部分可以进行条件编译。
条件编译的语法形式如下:
预处理器条件:
if行 文本 elif部分opt else部分opt #endif
if行:
#if 常量表达式
#ifdef 标识符
#ifndef 标识符
elif部分:
#elif 常量表达式
else部分:
else行 文本
else行:
#else
其中,每个条件编译指令(if行,elif行,else行及#endif)在程序中均单独占一行。
预处理器一次对#if以及后续的#elif行中的常量表达式进行计算,直到发现某个指令的常量表达式为非0值为止,这时将放弃值为0的指令行后面的文本。
常量表达式不为0的#if和#elif指令后面的文本将按照其他普通程序代码一样进行编译。
在这里,“文本”是指任何不属于条件编译指令结构的程序代码,它可以包含预处理指令,可以为空。
一旦预处理器发现某个#if或#elif条件编译指令中的常量表达式的值不为0,并选择其后的文本供以后的编译阶段使用时,后续的#elif和#else条件编译指令及相应的文本将被放弃。
如果所有常量表达式的值都是0,并且该条件编译指令链中包含一条#else指令,则将选择#else指令之后文本。
除了对条件编译指令的嵌套进行检查之外,条件编译指令的无效分支(即条件值为假的分支)控制的文本都将忽略。
#if和#elif中的常量表达式将执行通常的宏替换。
并且,任何下列形式的表达式:
define 标识符
或
define(标识符)
都将在执行宏扫描之前进行替换,如果该标识符在预处理器中已经定义,则用1替换它,否则,用0替换它。
预处理器进行宏扩展之后仍然存在的任何标识符都将用0来替换。
最后,每个整型常量都被预处理器认为其后跟有后缀L,一边把所有的算术运算都当作是在长整形或无符号长整形的操作数之间进行的运算。
进行上述处理之后的常量表达式(参见A.7.19节)满足下列限制条件:它必须是整型,并且其中不包含sizeof,强制类型转换运算符或枚举常量。
下列控制指令:
#ifdef 标识符
#ifndef 标识符
分别等价于:
#if defined 标识符
#if !defined 标识符
说明:#elif是ANSI中新引入的条件编译指令,但此前他已经在某些预处理器中实现了。
defined预处理器运算符也是ANSI中新引入的特征。
A.12.6 行控制 返回目录
为了便于其他预处器生成C语言程序,下列形式的指令行:
#line 常量 ″文件名″
#line 常量
将使编译器认为(出于错误诊断的目的):下一行源代码的行号是以十进制整形常量的形式给出的,并且,目前的输入文件是由该标识符命名的。
如果缺少带双引号的文件名部分,则将不改变当前编译的源文件的名字。
行中的宏将先进行扩展,然后再进行解释。
A.12.7 错误信息生成 返回目录
下列形式的预处理器控制指令:
#error 记号序列opt
将使预处理器打印包含记号序列的诊断信息。
A.12.8 pragma 返回目录
下列形式的控制命令:
#pragma 记号序列opt
将使预处理执行一个与具体实现相关的操作。
无法识别的pragma(编译指令)将被忽略。
A.12.9 空指令 返回目录
下列形式的预处理器行不执行任何操作:
#
A.12.10 预定义名字 返回目录
某些标识符是预定义的,扩展后将生成特定的信息。
它们同预处理表达式运算符defined 一样,不能取消定义或重新进行定义。
__LINE__ 包含当前源文件行数的十进制常量。
__FILE__ 包含正在编译的源文件名字的字符串字面值。
__DATE__ 包含编译日期的字符字面值,其形式为“Mmm dd yyyy”。
__TIME__ 包含编译日期的字符串字面值,其形式为“hh:mm:ss”。
__STDC__ 整形常量1。
只有在遵循标准的实现中该标识符才定义为1。
说明:#error与#pragma是ANSI标准中新引入的特征。
这些预定义的预处理器宏也是新引入的,其中的一些宏先前已经在某些编译器中实现。