`
jubincn
  • 浏览: 242486 次
  • 性别: Icon_minigender_1
  • 来自: 宁波
文章分类
社区版块
存档分类
最新评论

C预处理器简介

 
阅读更多


Cpreprocesser,简称cpp,是C编译器在编译源码之前用于转换代码的宏处理器。之所以称之为宏处理器,是因为通过cpp,你可以在程序中定义和使用宏。cpp是一种文本处理工具,因此也可以用于CC++Objective-C之外的源码。

初始处理

cpp的作用是对输入的文件做一系列的文本处理,这些文字处理是最先进行的。理论上,预处理中的各个操作的执行是有严格的顺序,但实际上GNUCPP将所有的操作一起执行。这些变换大体上可以分为这四个阶段:

  1. 将输入文件载入内存并分行

GNU CPP处理的是ASCII编码的字节流。GNUCPP还支持扩展的ASCII码,如ISOLatin-1UTF-8,目前不支持非7bitASCII编码字符。

不同的操作系统使用不同的方式来标记行的结束。GNUCPP接受ASCII序列LF,CR, CRLFLFCr作为换行符,但在同一个文件中要使用同一种换行符。如果文件的最后一行没有换行符,那么GNUCPP将自动为其补上换行符。


  1. 如果有三字词(trigraph),将其转为对应的单个字符

trigraph是个历史遗留问题,是为了使那些缺少一些C中字符的遗留系统使用C,目前很少使用,甚至一些编译器都不能正确地处理trigraphtrigraph共有9个,其对应关系如下图所示:


Trigraph: ??( ??) ??< ??> ??= ??/ ??' ??! ??-

   	Replacement:      [    ]    {    }    #    \    ^    |    ~
  1. 将标记为连续的多行合并为一个单行

标记为连续的多行指行末使用”/”标记的行,CPP将其删除并将下一行连接到此行。即使在”/”与换行符之间有空格,也不会有影响,标记的多行仍为标记的多行。

  1. 将所有的注释用空格代替

/*...*///标记的注释在此步会被CPP用空格代替。

断词(Tokenization)

在文本处理结束后,输入文件将被转化为一个词(token)序列,这些词(token)和C编译器中的词(token)大部分是对应的,但也有例外。这些词用空格分开,空格本身并不是词。

在断词的过程中如果出现多义性,那么CPP将采取贪心策略,从左侧开始尽量获得更长的词,如a+++++b将被分为a++ ++ + b

CPP中将输入文件断词后,除非使用##运算符,否则分词不会发生改变,如:

 #define foo() bar
     foo()baz
          ==> bar baz
     not
          ==> barbaz

尽管foo()bazfoo()baz之间没有空格,但因为对foo()做了定义,因此CPP将其作为两个词来处理,中间使用空格分离。

预处理token可以分为这五个大类:identifiers,preprocessing numbers, string literals, punctutators和其它。下面简单介绍这五个类:

Identifiers:预处理identifierC中的identifier是一样的,即:以下划线或字符开头,由字符、数字或下划线组成的序列。C中的预处理identifier只有一个关键字,defined

Preprocessingnumber与普通的数字定义不同。除C中的普通整型和浮点型常量外,还包括其它的一些表达方式。Preprocessingnumber指任何以可选的点和十进制数字开头的,包括字符、数字、下划线、点和指数的序列。指数包括这些:e+,e-,E+,E-,p+,p-,P+,andP-。预处理中的数字之所以这么定义,是想将预处理器从复杂的数定义中摆脱出来。

Stringliterals包括字符串常量、字符常量和头文件名。头文件名有两种表达方式,分别是”...”和<...>。使用”...”,预处理器会首先在当前目录寻找相关的头文件,再去系统路径寻找,使用<...>则直接去系统目录查找头文件。

punctutators包括所有CC++中标点(punctutators)。ASCII中除@,$,`这三个外,其它全部标点都是C中的标点。所有的两字符和三字符操作符都是标点,除此之外还有六个复合字符,他们的对应关系如下:

Digraph:        <%  %>  <:  :>  %:  %:%:
     	Punctuator:      {     }   [   ]   #   ##

预处理语言(ThePreprocessing Language

在完成分词后,从输入文件的得到的词流(tokenstream)可以传给编译器来处理,但如果在预处理语言中定义了一些操作,那么这么操作将先于编译执行。

预处理语言由要执行的指令(directives)和要展开的宏(macros)组成。其主要功能是:

  • 头文件的包含。#include头文件 指令将被相应的头文件内容替换。

  • 宏展开。不仅包括用户自定义的宏,还有一些系统自定义的宏。

  • 条件编译。

  • 行控制。

  • 诊断。


###

#:字符串化

#在宏的作用是”字符串化“。宏展开操作并不在处理形如这样”...”被引起的内容,这样在””之间的宏就不会展开。#宏操作符可以解决这个问题,#的作用是在其后面宏展开后,加上””,如:

#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语言预处理器.pdf

    C语言预处理器(C Preprocessor)是C编译过程中的一个重要组成部分,它在实际的编译之前对源代码进行处理,执行宏定义、条件编译、头文件包含等操作,为编译器提供更加清晰、易于管理和维护的代码。预处理器的引入极...

    Snort 2.9.8.2预处理器开发文档

    5. 修改 spp_profinet.c 文件,包括修改头文件、定义预处理器执行函数 ProfinetRTFunct()、添加预处理器到预处理器列表等。 二、注册预处理器 1. 在 spp_profinet.c 文件中,定义预处理器执行函数 ProfinetRTFunct...

    编译原理预处理器

    "编译原理预处理器"这一主题涵盖的知识点广泛,包括预处理器的基本概念、功能以及在实际编程中的应用。 预处理器(Preprocessor)主要负责在编译之前对源代码进行预处理,它的任务主要包括: 1. **宏定义**:预...

    优化秘籍:C语言预处理器宏的高级应用

    ### 优化秘籍:C语言预处理器宏的高级应用 #### C语言简介 C语言作为一门通用编程语言,自20世纪70年代初由丹尼斯·里奇(Dennis Ritchie)在贝尔实验室开发以来,一直因其高效性、灵活性和可移植性而受到广泛欢迎...

    VC6.0预处理器参考手册(中).pdf

    ##### 预处理器简介 预处理器是一种文本处理工具,用于处理源文件,在编译前对其进行修改。它不涉及语法分析,而是将源代码分割成不同的符号以便识别宏调用等元素。 ##### 预处理器的启动方式 预处理器可以通过...

    编译原理中 预处理器

    它们不是C/C++语言的一部分,而是由编译器的预处理器专门解析的。 1. `#define`:用于创建宏定义,可以将一个标识符替换为一个常量或表达式。这在编写代码时提供了便利,比如简化重复的计算或者创建可配置的代码段...

    预处理器介绍

    尽管有些C程序员十分依赖于预处理器,我依然建议适度地使用它,就像许多其他生活中的事物一样。现代的C语言编程风格呼吁减少对于处理器的依赖。在C++中,对语言的改变使得可以更进一步限制预处理器的使用。 &lt;br&gt;

    C语言中的预处理器高级特性:深度解析与应用实例

    C语言的预处理器提供了强大的高级特性,包括宏定义、条件编译、文件包含、编译器指令等。这些特性不仅提高了代码...通过本文的介绍,希望读者能够对C语言预处理器的高级特性有一个全面的了解,并在实际编程中灵活应用。

    C 预处理器介绍以及如何实现的

    C预处理器使用一种类似于C语言但又有所不同的语言来进行控制指令的编写。这种语言包括但不限于宏定义、条件编译指令等。这些指令通常以`#`开头,以区别于普通的C代码。 #### 2. 头文件 头文件包含了可以在多个...

    CPP.zip_decus_预处理器

    DECUS C预处理器可能包含了特定的扩展或优化,以适应DECUS C语言环境。例如,可能提供了特定的宏定义、条件编译指令或者处理 DECUS 特有的编译标志。不过,由于具体细节没有给出,这部分内容只能通过查阅 DECUS 的...

    C语言中的预处理器魔法:指令使用全指南

    # C 语言中的预处理器魔法:指令使用全指南 C 语言作为一种通用的编程语言,在计算机科学领域占据着举足轻重的地位。它由丹尼斯·里奇在 20 世纪 70 年代早期开发,因其高效性、灵活性和可移植性而受到广泛欢迎。C ...

    C/C++安全检查工具预处理器的设计与实现

    标题“C/C++安全检查工具预处理器的设计与实现”意味着本文将介绍如何设计和构建一个专门用于C/C++语言的安全检查工具中的预处理器部分。预处理器是编译过程中的第一步,它负责处理源代码文件中的预处理指令,如宏...

    C语言预处理器

    C语言预处理器 C 预处理器不是编译器的组成部分,但是它是编译过程中一个单独的步骤。简言之,C 预处理器只不过是一个文本替换工具而已,它们会指示编译器在实际编译之前完成所需的预处理。我们将把 C 预处理器(C ...

    14. 预处理器.docx

    预处理器是一种特殊的软件工具,它在C语言程序编译前进行一系列的处理工作。与其他编程语言相比,C语言及其衍生语言如C++,由于其依赖于预处理器这一特性而独具特色。 ##### 1.2 预处理器的主要功能 预处理器的...

    cpp-一个更强大的CC预处理器

    "cpp-一个更强大的CC预处理器"可能是指一种增强版的C/C++预处理器,它扩展了标准预处理器的功能,以提供更高级别的抽象和便利性。本文将深入探讨C/C++预处理器的基本概念、增强功能以及它们在实际开发中的应用。 预...

    Microsoft Visual C++ 6.0 预处理器参考手册

    Microsoft Visual C++ 6.0 预处理器是一个重要的工具,在编译C和C++程序前对其进行预处理。预处理器能够执行多种任务,如定义和取消定义宏、扩展宏、条件编译代码、插入文件以及指示编译时的错误信息等。本文档旨在...

    简介C/C++预处理器的一些工作

    就在被带到编译器那里之前,预处理器都会对你的源代码瞧上一瞧, 做一些格式化的工作,并执行任何你在源代码里面留给它来执行的指令. 像什么? 好吧,预处理器的指令就被叫做预处理器指令,而他们都以一个#开头. 像 #...

    c++ IDE 预处理器(csdn)————程序.pdf

    在Visual Studio中,预处理器的配置位于项目的“Properties”(属性)菜单下,通过“Configuration Properties” -&gt; “C/C++” -&gt; “Preprocessor”来访问。在这个页面,你可以找到“Preprocessor Definitions”选项...

    C语言文件操作、预处理器和宏课件.md

    ### C语言文件操作、预处理器和宏课件知识点解析 #### 文件操作 ##### 打开文件 在C语言中,`fopen`函数用于打开一个文件。它接收两个参数:第一个参数是要打开的文件名(通常为字符串),第二个参数是打开方式。...

    多重预处理GMRES-GMRES的扩展,允许使用多个预处理器

    当使用标准Krylov子空间方法求解线性方程组时,用户只能选择一个预处理器,尽管在许多情况下...Greif,C.,Rees,T.,Szyld,D.B.,具有多个预处理器的GMRES 也可作为UBC技术报告:UBC CS TR-2011-12,或Temple Math。

Global site tag (gtag.js) - Google Analytics