宏
⦁ 基本概念
C语言中可以利用宏定义实现文本的快速替换,注意:宏定义是单纯的文本替换,不检查语法是否合法。
C语言标准中提供了很多的预处理指令,比如#include、#pragma…以#开头的都属于预处理指令。
预处理指令指的是在gcc编译套件中的cpp预处理器对程序进行编译之前所做的一些动作,比如#include预处理指令就是在程序编译之前由预处理器把包含的头文件中的代码拷贝一份到源文件对应的位置,如果包含的文件中还有其他的预处理指令,会递归执行!
C语言标准中还提供了#define预处理指令,define是C语言关键字之一,中文具有定义的含义,所以利用#define预处理指令可以对某些表达式、某些常量、某些函数进行定义,其实就是给这些内容起一个可读性较高的名称。
定义格式
宏替换其实就是简单的文本替换,宏名称就是一个用户命名的特定的标识符,一般实际开发中宏名称都采用大写(潜规则)。 macro 宏
宏名称后面就是用来替换宏名称的替换列表,这个替换列表可以是常量、表达式、if语句以及函数等。
定义格式: #define 宏名称(大写) 替换列表 换行(一般就是用户按下回车)
用户在源文件中某个位置使用了宏,不管使用了多少次,在程序编译之前,预处理器都会把宏用替换列表进行替换,当然要注意,宏替换就是单纯的文本替换,预处理器并不会做任何检查,比如替换之后是否符合语法,语法的检查是由编译器在编译阶段进行的。
使用规则
注意:宏定义的作用域是针对整个文件有效,所以应该定义在源文件的开头部分,这样才可以在其他的函数中使用宏定义,另外,宏不是语句,所以不需要在末尾添加分号,如果添加分号,则分号也会被一起替换。
具体分类
C语言中宏定义的方案有三种,分别是无参数宏、带参数宏、无替换列表宏,具体如下所示:
(1) 无参数的宏定义
注意:除了用户自定义的宏之外,系统中也存在一些已经定义好的宏,比如常用的NULL就是一个宏,当然,C99标准中还有一些常用的系统预定义的宏:
(2) 带参数的宏定义
C语言标准中支持定义带参数的宏,带参数的宏的使用在语法上类似于函数调用,宏的参数由括号()进行包含,括号中如果有多个参数则需要通过逗号来分隔,另外,带参数的宏在定义的时候宏名称和参数列表之间不能空格,如下所示:
可以发现,带参数的宏和函数的形式很像,但是却完全不同,带参数的宏会在程序所有出现的位置进行展开,缺点是浪费了内存空间,但是节约了函数切换的时间。
(3) 无替换列表的宏
C语言中也允许只定义一个宏,这个宏可以没有替换列表,一般实际开发中都是对程序进行条件编译的情况下来使用。
条件编译指的是可以选择性的编译程序中的某段代码,也就是预处理器可以根据具体的条件来保留或者删除某段源程序。
可以理解为是类似于C语言的判断语句,只不过是使用C语言中的预处理指令来判断宏的有效性,有效性指的是宏是否为真以及宏是否存在,C语言中提供了多种预处理指令来实现条件编译。
A. #if 用于判断常量表达式是否成立,遵循“非0即真”原则,#if预处理指令作为条件编译
一般#if和#endif是结合一起使用的,经常用于程序中的调试,可以选择保留或注释代码块!
B. #ifdef 用于判断宏是否被定义,如果宏是提前定义好的,则该预处理指令是有效的,也需要和#endif一起使用
C.#if和#elif和#else和#endif 用于条件编译,可以通过常量表达式的多种状态来选择保留或者删除某些代码块
D. #ifndef和#endif 用于判断宏是否未定义,如果宏定义,则该代码块会被删除,如果宏未被定义,则该代码块可以保留
作用范围
思考:宏定义一般定义在源文件的开头,所以作用域是针对整个文件,但是有的时候如果只打算让某个宏只对某个函数有效,请问应该如何实现?
回答:可以实现,可以利用C语言标准中提供的预处理指令#undef,可以提前终止某个宏的作用域。
程序的编译过程
思考:什么叫做预处理阶段?预处理阶段和编译阶段有什么不同?源文件转换为可执行文件一共需要经历几个阶段?
预处理:
对源码进行简单的加工,GCC编译器会调用预处理器cpp对程序进行预处理,其实就是解释源程序中所有的预处理指令,如#include(文件包含)、#define(宏定义)、#if(条件编译)等以#号开头的预处理语句。
这些预处理指令将会在预处理阶段被解释掉,如会把被包含的文件拷贝进来,覆盖掉原来的#include语句,把所有的宏定义展开,所有的条件编译语句被执行,GCC还会把所有的注释删掉,添加必要的调试信息。
预处理指令: gcc -E xxx.c -o xxx.i 会生成预处理文件 xxx.i
编译:
就是对经过预处理之后的.i文件进行进一步翻译,也就是对语法、词法的分析,最终生成对应硬件平台的汇编文件,具体生成什么平台的汇编文件取决于编译器,比如X86平台使用gcc编译器,而ARM平台使用交叉编译工具arm-linux-gcc。
编译指令 : gcc -S xxx.i -o xxx.s 会生成汇编文件 xxx.s
汇编:
GCC编译器会调用汇编器as将汇编文件翻译成可重定位文件,其实就是把.s文件的汇编代码翻译为相应的指令。
编译指令 : gcc -c xxx.s -o xxx.o 会生成目标文件 xxx.o
链接:
经过汇编步骤后生成的.o文件其实是ELF格式的可重定位文件,虽然已经生成了指令流,但是需要重定位函数地址等,所以需要链接系统提供的标准C库和其他的gcc基本库文件等,并且还要把其他的.o文件一起进行链接。-lc -lgcc 是默认的,可以省略
编译指令:gcc hello.o -o hello -lc -lgcc 会生成可执行文件 xxx // l是lib的缩写