编译预处理

编译预处理指令

  • #开头的是编译预处理指令
  • 它们不是C语言的成分,但是C语言离不开它们
  • #define用来定义一个宏
  • #include用来包含一个头文件

C语言程序在编译之前会进行编译预处理,在编译预处理过程中会把所有的#define定义的宏进行替换。

C语言编译过程中会产生一些临时文件,在GCC编译器编译过程中如下:

main.c->main.i->main.s->main.o->a.out

  1. 由源代码main.c进行编译预处理得到main.i
  2. 由编译预处理后的代码文件main.i进行编译得到汇编代码文件main.s
  3. 汇编代码文件main.s做汇编得到目标代码文件main.o
  4. 目标代码文件main.o进行链接形成可执行文件a.out

#define

  • #define <名字><值>
  • 注意没有结尾的分号,因为不是C的语句
  • 名字必须是一个单词,值可以是各种东西
  • 在C语言的编译器开始编译之前,编译预处理程序会把程序中的名字换成对应的值,仅仅是做的完全的文本替换
  • 使用gcc –save-temps可以保存编译过程中的临时文件

  • 如果一个宏的值中有其他的宏的名字,也是会被替换的
  • 如果一个宏的值超过一行,最后一行之前的行末要加\
  • 宏的值后面出现的注释不会被当作宏的值的一部分

没有值的宏

  • #define _DEBUG
  • 这类宏是用于条件编译的,后面有其他的编译预处理指令来检查这个宏是否已经被定义过了
  • 在金山WPS实习开发WPS for Mac的时候遇到过,只有在Mac环境下编译才执行的一段代码

预定义的宏

  • __func__ //函数的函数名
  • __LINE__ //源代码文件的行号
  • __FILE__ //源代码文件的文件名
  • __DATE__ //编译时的日期
  • __TIME__ //编译时的时间
  • __STDC__ //判断该文件是不是标准C程序,当要求程序严格遵循ANSIC标准时该标识符被赋值为1
#include<stdio.h>

int dxf(void);

int main(int argc,int *argv[])
{
    dxf();
    return 0;
}

int dxf(void)
{
    printf("%s:%d\n",__FILE__,__LINE__);//输出源代码文件名和目前的行号
    printf("%s\n",__func__);//输出函数名
    printf("%s,%s\n",__DATE__,__TIME__);//输出编译的日期和时间
}

 

运行结果如下:

带参数的宏和像函数的宏

  • #define cube(x) ((x)*(x)*(x))
  • 宏可以带参数

由于预编译过程中#define仅仅是简单的文本替换,所以容易出现运算优先级问题,因此在定义带参数的宏的时候应该遵循一些原则

带参数的宏的原则

  • 一切都要括号(整个值要括号,参数出现的每个地方都要括号)
  • #define RADTODEG(x) ((x)*57.29578)

带参数的宏

  • 可以带多个参数,如#define MIN(a,b) ((a)>(b)?(b):(a))
  • 也可以组合嵌套使用其他宏
  • 在大型程序的代码中使用非常普遍
  • 部分宏会被inline函数替代

宏的缺点

  • 宏的参数没有类型检查,处理不了特殊的输入,而内联函数inline的引入正是为了解决这个问题

宏展开的灵活运用

在Arduino的Ethernet库的w5100.cpp里有这样的函数调用:

writeTMSR(0x55);

但是遍寻整个.cpp和对应的w5100.h也找不到这个writeTMSR()函数,即使把所有的源代码目录拿来搜索一遍都没有。但是,编译显然是通过了的,那么,这个函数在哪里呢?

在w5100.h,我们发现了这样的代码:

#define __GP_REGISTER8(name, address)             \
  static inline void write##name(uint8_t _data) { \
    write(address, _data);                        \
  }                                               \
  static inline uint8_t read##name() {            \
    return read(address);                         \
  }

于是,在w5100.h里接下去的代码:

__GP_REGISTER8 (TMSR,   0x001B);    // Transmit memory size

在编译预处理后,就会被展开成为:

static inline void writeTMSR(uint8_t _data) {
    write(0x001B, _data);            
}
static inline uint8_t readTMSR() {
    return read(0x001B);             
}

其中##是一种分隔连接方式,它的作用是先分隔,然后进行强制连接。

1 comment

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注