Cpreprocesser,简称cpp,是C编译器在编译源码之前用于转换代码的宏处理器。之所以称之为宏处理器,是因为通过cpp,你可以在程序中定义和使用宏。cpp是一种文本处理工具,因此也可以用于C、C++和Objective-C之外的源码。
初始处理
cpp的作用是对输入的文件做一系列的文本处理,这些文字处理是最先进行的。理论上,预处理中的各个操作的执行是有严格的顺序,但实际上GNU的CPP将所有的操作一起执行。这些变换大体上可以分为这四个阶段:
-
将输入文件载入内存并分行
GNU CPP处理的是ASCII编码的字节流。GNUCPP还支持扩展的ASCII码,如ISOLatin-1和UTF-8,目前不支持非7bit的ASCII编码字符。
不同的操作系统使用不同的方式来标记行的结束。GNUCPP接受ASCII序列LF,CR,
CRLF和LFCr作为换行符,但在同一个文件中要使用同一种换行符。如果文件的最后一行没有换行符,那么GNUCPP将自动为其补上换行符。
-
如果有三字词(trigraph),将其转为对应的单个字符
trigraph是个历史遗留问题,是为了使那些缺少一些C中字符的遗留系统使用C,目前很少使用,甚至一些编译器都不能正确地处理trigraph。trigraph共有9个,其对应关系如下图所示:
Trigraph: ??( ??) ??< ??> ??= ??/
??' ??! ??-
Replacement: [ ] { } # \ ^ | ~
-
将标记为连续的多行合并为一个单行
标记为连续的多行指行末使用”/”标记的行,CPP将其删除并将下一行连接到此行。即使在”/”与换行符之间有空格,也不会有影响,标记的多行仍为标记的多行。
-
将所有的注释用空格代替
/*...*/和//标记的注释在此步会被CPP用空格代替。
断词(Tokenization)
在文本处理结束后,输入文件将被转化为一个词(token)序列,这些词(token)和C编译器中的词(token)大部分是对应的,但也有例外。这些词用空格分开,空格本身并不是词。
在断词的过程中如果出现多义性,那么CPP将采取贪心策略,从左侧开始尽量获得更长的词,如a+++++b将被分为a++
++ + b。
在CPP中将输入文件断词后,除非使用##运算符,否则分词不会发生改变,如:
#define foo() bar
foo()baz
==> bar baz
not
==> barbaz
尽管foo()baz中foo()和baz之间没有空格,但因为对foo()做了定义,因此CPP将其作为两个词来处理,中间使用空格分离。
预处理token可以分为这五个大类:identifiers,preprocessing numbers, string literals, punctutators和其它。下面简单介绍这五个类:
Identifiers:预处理identifier与C中的identifier是一样的,即:以下划线或字符开头,由字符、数字或下划线组成的序列。C中的预处理identifier只有一个关键字,defined。
Preprocessingnumber:与普通的数字定义不同。除C中的普通整型和浮点型常量外,还包括其它的一些表达方式。Preprocessingnumber指任何以可选的点和十进制数字开头的,包括字符、数字、下划线、点和指数的序列。指数包括这些:e+
,e-
,E+
,E-
,p+
,p-
,P+
,andP-
。预处理中的数字之所以这么定义,是想将预处理器从复杂的数定义中摆脱出来。
Stringliterals
:
包括字符串常量、字符常量和头文件名。头文件名有两种表达方式,分别是”...
”和<...>
。使用”...
”,预处理器会首先在当前目录寻找相关的头文件,再去系统路径寻找,使用<...>
则直接去系统目录查找头文件。
punctutators:包括所有C和C++中标点(punctutators)。ASCII中除@
,$
,和`
这三个外,其它全部标点都是C
中的标点。所有的两字符和三字符操作符都是标点,
除此之外还有六个复合字符,他们的对应关系如下:
Digraph: <% %> <: :> %: %:%:
Punctuator: { } [ ] # ##
预处理语言(ThePreprocessing Language
)
在完成分词后,从输入文件的得到的词流(tokenstream)可以传给编译器来处理,但如果在预处理语言中定义了一些操作,那么这么操作将先于编译执行。
预处理语言由要执行的指令(directives)和要展开的宏(macros)组成。其主要功能是:
#与##
#:字符串化
#在宏的作用是”字符串化“。宏展开操作并不在处理形如这样”...”被引起的内容,这样在””之间的宏就不会展开。#宏操作符可以解决这个问题,#的作用是在其后面宏展开后,加上””,如:
#define QUOTEME(x) #x
下面的代码:
printf("%s\n",QUOTEME(1+2));
将被展开为:
printf("%s\n", "1+2");
#在字符串化宏参数时,需要一些技巧,负责宏参数将不会被展开,如:
#define FOO bar
下面的代码:
printf("FOO=%s\n", QUOTEME(FOO));
将被展开为:
printf("FOO=%s\n", "FOO");
#经常用于输出代码所在行,如:
#define QUOTEME_(x) #x
#define QUOTEME(x) QUOTEME_(x)
现在
QUOTEME(__LINE__);
将输出:
"34"
#常被定义为CHAR,从而增强可读性,如:
#define CHAR(X) #X[0]
使得:
printf("%c\n", CHAR(a))
printf("%c\n", CHAR(b))
输出:
a
b
##:字连接符(tokenconcatenation)
##可以在预处理阶段将两个词(token)连接起来,如:
#define MYCASE(item,id) \
case id: \
item##_##id = id;\
break
switch(x) {
MYCASE(widget,23);
}
宏展开后,MYCASE(widget,23);将得到:
case 23:
widget_23 = 23;
break;
在使用##处理参数时,要注意增加一个转换函数,如下:
enum {
OlderSmall = 0,
NewerLarge = 1
};
#define Older Newer
#define Small Large
#define replace_1(Older, Small) Older##Small
#define replace_2(Older, Small) replace_1(Older, Small)
void printout()
{
// replace_1(Older, Small) becomes OlderSmall (not NewerLarge),
// despite the #define calls above.
printf("Check 1: %d\n", replace_1(Older, Small));
// The parameters to replace_2 are substituted before the call
// to replace_1, so we get NewerLarge.
printf("Check 2: %d\n", replace_2(Older, Small));
}
这段代码的执行结果:
Check 1: 0
Check 2: 1
分享到:
相关推荐
c preprocessor c preprocessor
尽管有些C程序员十分依赖于预处理器,我依然建议适度地使用它,就像许多其他生活中的事物一样。现代的C语言编程风格呼吁减少对于处理器的依赖。在C++中,对语言的改变使得可以更进一步限制预处理器的使用。 <br>
这份文件是关于C语言预处理器的详细介绍,包括其工作原理、主要功能、GCC编译过程中预处理器的角色以及预处理指令的使用和规则。以下是文件的核心内容概要: 1. **预处理器工作原理**: 2. **GCC编译过程及常用选项*...
如果你要做编译原理的作业,要做预处理,此乃神器
C语言预处理器 C 预处理器不是编译器的组成部分,但是它是编译过程中一个单独的步骤。简言之,C 预处理器只不过是一个文本替换工具而已,它们会指示编译器在实际编译之前完成所需的预处理。我们将把 C 预处理器(C ...
就在被带到编译器那里之前,预处理器都会对你的源代码瞧上一瞧, 做一些格式化的工作,并执行任何你在源代码里面留给它来执行的指令. 像什么? 好吧,预处理器的指令就被叫做预处理器指令,而他们都以一个#开头. 像 #...
DECUS C 语言源程序预处理器
然后详细讲解了预处理器和宏的相关知识,如宏定义、条件编译、头文件引用等,也给出了示例代码进行解释说明。最后提供了文件操作和宏的综合应用示例。内容结构清晰,知识点突出。 适合人群: 学习C语言的学生或者初级...
本资料描述了C++预处理器做了那些工作,以及如何做的。特别是一些编程的注意事项,对于编程也有提高!
但是,如果您是从 C 编程背景开始使用 MATLAB,您可能想知道 MATLAB 的预处理器在哪里。 好吧,MATLAB 没有自带。MPP 旨在填补这一空白。 的确,您可以在 MATLAB 代码上使用您最喜欢的 C 预处理器,但有几个原因会...
很棒的C预处理器 很棒的C预处理器清单 专案 一种用于C99预处理程序元编程的功能语言。 具有求和类型的C99 -C宏预处理器上的编译时LISP解释器。 预处理模块 vmd-Boost.org VMD模块 -order-pp预处理程序库(chaos...
一个更强大的C / C 预处理器
C 预处理器不是编译器的组成部分,是编译过程中一个单独的步骤。C预处理器只是一个文本替换工具,它会指示编译器在实际编译之前完成所需的预处理。 所有的预处理器命令都是以井号(#)开头。它必须是第一个非空字符...
提出了一种基于分析器自动生成工具Antlr构造的C/C++安全检查工具预处理器的方法。
预处理器是一些指令,指示编译器在实际编译之前所需完成的预处理。 所有的预处理器指令都是以井号(#)开头,只有空格字符可以出现在预处理指令之前。预处理指令不是 C++ 语句,所以它们不会以分号(;)结尾。 我们...
CParser, 在纯Lua中,一个 compact 预处理器和声明解析器编写 CParser这个纯 Lua MODULE 实现了标准符合预处理程序的标准C 预处理器和解析器,它提供了对C 头或者C 程序文件中所有全局声明和定义的友好描述。...
helloworld.c程序是从一个高级C程序开始的,为了在系统上运行此程序,每条C语言都必须被其他程序转化为一系列的低级机器语言指令,...执行这四个阶段的程序:预处理器,编译器,汇编器和链接器,一起构成了编译系统。
准备:预处理器
应用程序用 Java 编写的类似 C 的预处理器,具有 C 预处理器之外的功能
C预处理器实用程序 介绍 zen.h是C预处理程序宏的集合。 目前,它仅提供了一个小型框架,仅用于通过C预处理器管理32位计数器,但将来可能会扩展。 除了内置的(和非标准的) __COUNTER__之外,为C预处理器实现新颖的...