预处理(或称预编译)是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所作的工作。预处理指令指示在程序正式编译前就由编译器进行的操作,可放在程序中任何位置。
当我们写了一个代码,从一个文本文件的代码到最后成为一个可执行程序的过程中,要经过
编译+链接->.exe文件(二进制文件)
一个工程中每个源文件都会经过编译器生成目标文件(.obj,linux下是.o文件),经过链接器(链接链接库和目标文件)输出.exe文件
其中编译分为三个部分:预编译 -> 编译 -> 汇编
预编译的作用
1)#include头文件的包含
2)注释删除,使用空格替换注释
3)#define进行替换
在linux下,采用gcc -E的指令将源文件进行预编译
在目录下会生成test.i文件
可以看到预编译阶段完成了上述的描述的功能。
预编译完成之后,下一步是将test.i进行编译。
编译的作用是将C代码翻译成汇编代码,并对代码进行语法分析、词法分析、语义分析、符号汇总
在linux下采用gcc -S将test.i文件编译生成test.s文件
可以看到test.s里面写的都是汇编代码。
同样的编译的最后一步汇编是将test.s文件里面的汇编转换为二进制代码,并且形成符号表(将之前的符号汇总与相应的地址对应起来形成映射表)
linux下采用gcc -c的指令
默认生成test.o文件
因为采用的是文本编辑器vim打开的,所以看到的二进制显示出来的都是乱码。
要生成可执行程序还差一步链接工作
链接器的两个主要任务是符号解析和重定位,符号解析将目标文件中的每个全局符号都绑定到一个唯一的定义,而重定位确定每个符号的最终内存地址,并修改对那些目标的引用。
链接可以在编译时由静态编译器来完成,也可以在加载时和运行时由动态链接器来完成。
在linux下采用gcc -o的命令
针对预处理(预编译),C语言提供多种预处理功能
1、预定义符号
__FILE__:表示代码所在的路径
__LINE__:表示代码所在行数
__DATE__:获取日期
__TIME__:获取时间
printf("%s\n", __FILE__);//打印代码所在文件
printf("%d\n", __LINE__);//打印该句代码所在的行数
printf("%s\n", __DATE__);//打印当前日期
printf("%s\n", __TIME__);//打印当前时间
2、预处理指令
包括:
#define,#include,#program,#if,#endif,#ifdef,#ifdef等等
#define可以声明常量、表达式,#define定义的宏是直接替换,而不是赋值
比如说下面这段代码
#define Add(a,b) a+b
printf("%d", 5 * Add(3, 4));
这题的结果是19而不是35。在预编译之后,printf语句会变成printf("%d", 5 * 3 + 4);
采用宏的方式,使用#也可以将一个宏参数变成字符串,例如
#define PRINT(X) printf("I love "#X"\n")
PRINT(CSDN);
运行结果:I love CSDN
##在宏中的作用
把位于他两边的符号合成一个符号
#define sum##num等效于#define sumnum
宏的好处没有类型的概念,直接替换在某些场合使用宏可以避免类型不匹配的问题,且效率比函数高,没有函数调用和返回的开销。
宏的缺点就是无法调试,因为没有类型,所以不够严谨,无法做类型检查。
条件编译
1、#ifdef、#ifndef和#endif
#ifdef DEBUG//如果定义了DEBUG就执行下面的代码
printf("DEBUG\n");
#endif
#ifndef RELEASE//如果没有定义了RELEASE就执行下面的代码
printf("RELEASE\n");
#endif
#ifndef、#define、#endif或者#program once可以解决头文件重复多次包含
#ifndef _TEST_H_
define _TEST_H_
//…………
#endif
//或者采用program once
#program once
2、#if、#elif和#else
#if 表达式
//……
#elif 表达式
//……
#else 表达式
//……
#endif 表达式
//如果表达式为真则参与编译
#include
#include<>与#include" "的区别
<>是直接去标准库函数路径下去找 ,如果找不到就报错。
" "是首先在源文件所在目录查找,如果找不到,就去标准库函数路径底下去找,如果再找不到,就会报错。""的效率要低一些。
offsetof
计算结构体成员偏移量的宏
如果不清楚结构体大小如何计算,可以参考这篇文章
https://blog.csdn.net/weixin_43164548/article/details/118405163?spm=1001.2014.3001.5501https://blog.csdn.net/weixin_43164548/article/details/118405163?spm=1001.2014.3001.5501采用C语言官方的例子
struct foo {
char a;
char b[10];
char c;
};
int main ()
{
printf ("offsetof(struct foo,a) is %d\n",(int)offsetof(struct foo,a));
printf ("offsetof(struct foo,b) is %d\n",(int)offsetof(struct foo,b));
printf ("offsetof(struct foo,c) is %d\n",(int)offsetof(struct foo,c));
return 0;
}
运行结果:
offsetof(struct foo, a) is 0
offsetof(struct foo, b) is 1
offsetof(struct foo, c) is 11
那么offsetof是如何实现的呢?
#define OFFECTOF(struct_name, member_name) (int)&((struct_name* )0->member_name)
//直接假设从起始地址0开始,取出每个成员的地址就是偏移量