第46课 - 函数与宏分析
第46课 - 函数与宏分析
1. 函数与宏
(1)宏是由预处理器直接替换展开的,编译器不知道宏的存在,因此参数无法进行类型检查
函数是由编译器直接编译的实体,调用行为由编译器决定
(2)多次使用宏会增大代码量,最终导致可执行程序的体积增大,对于嵌入式设备而言,设备资源有限,这个还是比较重要的
函数是跳转执行的,内存中只有一份函数体存在,不存在宏的问题
(3)宏的效率比函数高,因为宏是文本替换,没有调用开销 // 虽然宏的效率比函数稍高但副作用很大,因此,可以用函数完成的功能绝对不用宏
函数调用会创建活动记录,效率不如宏
(4)函数可以递归调用,但宏的定义中不能出现递归定义
【函数与宏】
1 #include <stdio.h> 2 3 // 使用宏将一片内存区域置0 4 #define RESET(p, len) while(len > 0) \ 5 ((char *)p)[--len] = 0 6 7 // 使用函数将一片内存区域置0 8 void reset(void *p, int len) 9 { 10 while(len > 0) 11 ((char *)p)[--len] = 0; 12 13 } 14 15 int main() 16 { 17 int a[5] = {1, 2, 3, 4, 5}; 18 int len = sizeof(a); 19 int i = 0; 20 21 /* 22 下面的宏和函数都可以实现置0的功能 23 但是假如使用 RESET(10, len),这个在编译期间是不错报错的,宏不会检查参数的类型 24 使用reset(10, len)函数在编译时就会有warning,提示传参类型不符 25 */ 26 // reset(a, len); 27 // RESET(a, len); 28 29 for(i = 0; i < 5; i++) { 30 printf("a[%d] = %d\n", i, a[i]); 31 } 32 33 return 0; 34 }
【宏的副作用】
1 #include <stdio.h> 2 3 #define _ADD_(a, b) a + b 4 #define _MUL_(a, b) a * b 5 #define _MIN_(a, b) ((a) < (b) ? (a) : (b)) 6 7 int main() 8 { 9 int i = 1; 10 int j = 10; 11 12 // 预处理结果:printf("%d\n", 1 + 2 * 3 + 4); 13 // 预期: (1 + 2) * (3 + 4) ==> 21 14 // 实际: 1 + 2 * 3 + 4 ==> 11 15 printf("%d\n", _MUL_(_ADD_(1, 2), _ADD_(3, 4))); 16 17 // 预处理结果:printf("%d\n", ((i++) < (j) ? (i++) : (j))); 18 // 预期: 1 < 10? 1 : 10 ==> 1 19 // 实际: (i++) < (j) ? (i++) : (b) ==> 2 20 printf("%d\n", _MIN_(i++, j)); 21 22 return 0; 23 }
2. 宏的妙用
前面讲了宏的很多副作用和缺点,那宏是不是一无是处呢?绝对不是这样的,在C语言中宏有很多妙用。
(1)用于生成一些常规性的代码,比如下面代码中的LOG_INT、LOG_CHAR、LOG_FLOAT、LOG_POINTER
(2)封装函数,加上类型信息,比如下面代码中的MALLOC的实现
【宏的妙用】
1 #include <stdio.h> 2 #include <malloc.h> 3 4 #define MALLOC(type, x) (type*)malloc(sizeof(type) * x) 5 #define FREE(p) (free(p), p = NULL) 6 7 #define LOG_INT(i) printf("%s = %d\n", #i, i) 8 #define LOG_CHAR(c) printf("%s = %c\n", #c, c) 9 #define LOG_FLOAT(f) printf("%s = %f\n", #f, f) 10 #define LOG_POINTER(p) printf("%s = %p\n", #p, p) 11 #define LOG_STRING(s) printf("%s = %s\n", #s, s) 12 13 #define FOREACH(i, n) while(1){ int i = 0, l = n;for(i = 0; i < l; i++) // 使用while是为了定义一个scope,局部变量都在其中 14 #define BEGIN { 15 #define END }break;} 16 17 int main() 18 { 19 int* pi = MALLOC(int, 5); // ①可以指定数据类型,代码可读性更强 ②返回的void *指针已经进行了强制类型转换 20 char* str = "2020-01-14 22:38:27"; 21 22 LOG_STRING(str); // str = 2020-01-14 22:38:27 23 24 LOG_POINTER(pi); // pi = 0x74f010 25 26 /* 27 while(1){ int k = 0, l = 5;for(k = 0; k < l; k++) 28 { 29 pi[k] = k + 1; 30 }break;} 31 */ 32 FOREACH(k, 5) 33 BEGIN 34 pi[k] = k + 1; // 对pi对应的堆空间赋值 35 END 36 37 /* 38 while(1){ int k = 0, l = 5; for(k = 0; k < l; k++) 39 { 40 int value = pi[k]; 41 printf("%s = %d\n", "value", value); 42 }break;} 43 */ 44 FOREACH(k, 5) 45 BEGIN 46 int value = pi[k]; // 遍历pi对应的堆空间 47 LOG_INT(value); 48 END 49 50 FREE(pi); // 安全的释放动态内存 51 52 LOG_POINTER(pi); // pi = nil 53 54 return 0; 55 }