4-预处理详解

mac2025-01-08  35

1.预处理概述

意义 (1)预处理就是将一些繁杂的代码(头文件、宏定义、条件编译、注释等)提前展开或清除,将核心的代码编译交给编译器来编译。 (2)常见的预处理 /*头文件*/ #include <xxx.h> #include "xxx.h" /*宏定义*/ #define xxx yyy /*注释*/ void func(void); //xxxxxx /*条件编译*/ #ifndef xxx #define xxx #endif 宏定义的预处理 (1)宏定义被预处理后,原来的宏定义的代码消失,程序中所有被宏定义的字符被替换为原来的值; (2)因此在编译器的编译阶段已经不包含宏定义了;但typedef定义的代码不变,不属于预处理范围内;头文件的预处理 (1)头文件包含被预处理后,原来的包含头文件的代码消失,预处理会将所包含的头文件里面的内容全部展开到程序中; (2)#include <xxx.h>与#include "xxx.h"的区别:前者是用来包含系统提供的头文件,C语言编译器(预处理器、汇编器、链接器等统称为编译器)会自动到系统指定的目录下寻找相关头文件; 后者编译器则会先到当前目录下寻找头文件,若未找到再到系统目录下寻找该头文件,若都没有,则会报无此头文件; (3)编译器还允许用-I来附加指定其他的包含路径)去寻找这个头文件(隐含意思就是不会找当前目录下)注释的预处理 由于注释的作用是给程序员看的而不是cpu,因此编译器再预处理时会直接删掉注释来减少内存的占用;条件编译的预处理 (1)常见的两种条件判定分别是#ifdef/#ifndef xxx 和 #if (xxx),二者区别:前者在预处理时会判断在#ifdef/#ifnedf xxx代码前,xxx是否已经被定义,若被定义/未定义则条件成立,执行相关的代码; (2)后者类似于C中的if语句,判断(xxx)中的值为true或false,执行相关代码; #include <stdio.h> //1、预处理会从系统目录中寻找stdio.h文件,2、将该行代码删除,3、将stdio.h文件展开到该.c文件中 #include "test.h" //1、预处理会先从当前目录中寻找test.h文件,若未找到则再到系统目录中寻找; #define dp_int int * //预处理会将后面所有代码的dp_int字符替换回int *,并将此行代码删除 typedef char* tp_char; //预处理不会处理该行代码,也不会处理此后的tp_char字符 int main(void) { int a = 0; d_int a, b; //预处理会将该行代码替换为int *a, b;即d_int a替换为int *类型,将b替换为int类型:即int *a, b; t_char c, d; //编译器会将c和d都替换为char *类型 #ifndef dp_int //预处理器会判断是否未定义dp_int printf("not defint d_int\n"); //若未定义则执行此行代码 #else //若未定义,则预处理器会直接删除这条判断相关的代码,若已经定义,则预处理器会删除上面未定义的代码,然后执行这条代码 printf("define d_number\n"); //则执行此行代码 #endif //结束条件判断 #if (a == 0) printf("true\n"); #else printf("false\n"); #endif return 0; }

2.宏定义详解

宏定义概述 (1)宏定义的作用就是原封不动的替换,而且仅限于字符替换,即便替换后会导致后面的代码逻辑或语法错误,宏定义也并不关心; (2)宏定义中 标识符表示其后面的所有成分,即标识符后所有成分之间即便有空格也算作一个整体; (3)宏定义的替换可以递归替换,直到最后的成分不是宏为止; #define n 1 0 #define m n int a[m] = {0}; //预处理后 int a[1 0] = {0}; //1、后面编译器编译时必然会报语法错误,但预处理对于宏定义的处理只时原封不动替换,对于其他并不关心 //2、尽管1和0之间有空格,但n仍表示为 ”1 0“ //3、n替换 ”1 0“,m替换n,最终m代表 “1 0” 宏定义示例 (1)MAX宏,求两者中最大者:注意宏定义时所有的独立元素都要加(),否则直接替换后可能会导致运算出错 #define max(a, b) (((a) > (b))? (a) : (b)) int func(void) { int x = 2, y = 3; max(x+2, 3); reutrn 0 }

(2)宏定义求一年中有多少秒:注意类型的转换,因为C中默认时int类型,而秒数超出int范围

#define second (365*24*60*60UL) \\将60转为unsigned long类型 #define second (365*24*60*60)UL \\错误,编译器无法通过

(3)谨记宏定义的含义就是完全替换

#define FUNC (((a) > (b)) ? (a) : (b)) int func(int a, int b) { return a > b? a: b; } int main(int args, char *argv[]) { int a = 1, b = 3 int c = FUNC(++a, b); printf("a = %d\nb = %d\nc = %d\n", a, b, c); //输出为3 3 3 func(++a, b); printf("a = %d\nb = %d\nc = %d\n", a, b, c); //输出为2 3 2 } 带参宏与带参函数 (1)带参宏预处理时会将宏原地展开,运行时直接在原地运行;而带参函数运行时会调用子函数,通过函数名(指针)跳转到子函数地址去执行,调用开销大,若子函数较小时,效率不如带参宏; (2)带参函数运行时,结果的返回值是在定义函数时就必须设定的,因此在返回结果时,编译器会自动检查返回值的类型是否正确; (3)带参宏计算的结果返回值不包含数据类型,预处理时仅进行字符替换,因此也不会报任何警告错误,但运行结果可能会因为数据的类型不匹配等导致结果错误; /**********带参函数************/ #define max(a, b) (((a) > (b))? (a) : (b)) int func(int a, int b) //由于a 和 b的类型与int 不匹配,因此编译器警告 { if(a > b) return a; else return b; } int main(void) { float a = 3.14; float b = 6.18; float c = func(a, b); //由于形参和实参的类型 不匹配,因此编译器警告 int d = max(a, b); //由于带参宏返回值不附带数据类型,因此编译器不报任何异常 printf("d = %f\n", d); //此处结果是错误的 } 宏定义来实现条件编译:debug宏 (#define #undef #ifdef) #define DEBUG //定义一个宏 #undef DEBUG //取消这个宏定义 #ifdef DEBUG //若定义了这个宏 #define DEBUG(format,...) printf("file:"__FILE__", line:%d, "format"\n", __LINE__, ##__VA_ARGS__) //用debug代替 print函数 #else //若没定义 #define DEBUG(format...) //debug就是空 #endif int main(void) { char str[] = "hello"; DEBUG("%s", str); } //输出结果:file:main.c, line:16, test, hello offsetof宏与container_of宏 (1)offsetof宏:用来计算结构体某成员指针与结构体指针的偏移量: #define offsetof(type, member) (size_t)&(((type*)0)->member) // ((type*)0):将0地址强制类型转换为指针类型,指向一个type类型的结构体,即0地址为结构体的首地址( int *0 <==> int *p = 0 //p中存放着某个int变量的地址,将2赋给p即将2赋给int变量的地址即2中存放着int变量); //1、(((type*)0)->member):得到通过结构体指针得到member成员的空间(参数名); //2、&(((type*)0)->member):得到member成员的地址; //3、(size_t)&(((type*)0)->member):将member成员的地址转为int类型即偏移量 //4、因为type结构体的首地址为0,因此member成员的地址就是member成员地址的偏移量

(2)container_of宏:通过结构体某成员的指针计算结构体指针:

#define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) //ptr:某成员的指针;type:结构体类型;member:某成员名 //1、( ((type *)0)->member ):得到某成员在内存的空间; //2、const typeof( ((type *)0)->member ):获取member的类型 //typeof是根据变量的值获取变量类型的类型 //3、const typeof( ((type *)0)->member ) *__mptr:定义一个名为__mptr,类型为指向member的类型的指针; //4、const typeof( ((type *)0)->member ) *__mptr = (ptr):将member的指针赋给之前定义的__mptr变量 //5、offsetof(type,member):获取当前成员的指针相对于结构体指针的偏移; //6、( (char *)__mptr - offsetof(type,member) ):用当前的指针地址-指针偏移 == 结构体的起始地址(此时为char *类型的地址) //7、(type *)( (char *)__mptr - offsetof(type,member) ):将当前的char*类型地址转化为结构体type类型。 内联函数和inline关键字 (1)内联函数就是在定义函数的前面加inline关键字,来实现该函数既可以原地展开,又可以通过编译器来检查参数和返回值的类型。 (2)一般内联函数都是用在代码比较少的小型函数上。 int func(int a, int b) //由于a 和 b的类型与int 不匹配,因此编译器警告 { if(a > b) return a; else return b; } int main(void) { float a = 3.14; float b = 6.18; float c = func(a, b); //此函数运行时会在原地展开运行而非跳转,同时编译器也会报警告 printf("c = %f\n",c); }
最新回复(0)