1 宏的一些深入用法的例子
大家可能感觉宏比较简单,所以大家往往容易忽略宏的存在,实际上在很多的复杂C源码中,往往可以经常看到各种宏的比较深入的用法,所以我们这里举一些宏的比较高级的、比较深入的用法的例子,举这些例子的目的有三个
第一个:提高对宏的认识,希望大家能够适应c源码中宏的深入用法。
第二个:向大家如何分析这些比较复杂的宏,当自己遇到类似比较复杂的后,能够自己去分析这些宏。
第三个:希望大家也能够学会在自己的代码中,按照类似的方式去模仿宏的比较深入的用法,提高自己的代码质量。
1.1 例子1:使用宏来代替简短函数
比如:
#include <stdio.h> void exchange(int *p1, int *p2) { int tmp = 0; tmp = *p1; *p1 = *p2; *p2 = tmp; } int main(void) { int a = 10; int b = 30; exchange(&a, &b); printf("a=%d, b=%d\n", a, b); return 0; }
(1)例子中exchange函数就是一个简短函数
那么什么是简短函数呢,如何判断一个函数是不是简短函数呢?
1) 代码只有1~5行左右
2) 函数中没有循环
因为如果有循环的话,也相当于有很多的代码,不过如果你的循环要是非常短的话,比如只循环3~4次,累计的代码量也就只有5行左右的话,也算是简短函数,不过一般来说,我们并不把有循环的函数算作是就简短函数。
(2)简短函数缺点:调用开销比较大
1)时间开销:调用时跳转到被调函数处执行,函数执行完毕后,返回到调用处,这些都是需要时间的
2)空间开销:调用函数时,往往需要在栈中为形参开辟空间,所以有空间开销而且开辟和释放形参的空间,也是需要时间的,也有时间开销。
所以对于简短函数来说,函数调用的开销甚至都有可能 > 那1~5行代码的运行开销,所以说如果你在程序中有大量的简短函数的话,会非常影响你的程序质量,特别是当这个简单函数会被频繁调用时,累积的开销就更大了,所以这个时候就可以使用“带参宏”来代替了。
(3)使用带参宏来代替简短函数
helloworld.c
#include <stdio.h> #define EXCHANGE(p1, p2) \ int tmp = 0;\ tmp = *p1;\ *p1 = *p2;\ *p2 = tmp;\ int main(void) { int a = 10; int b = 30; EXCHANGE(&a, &b); printf("a=%d, b=%d\n", a, b); return 0; }
1)查看预编译结果:gcc -E helloworld.c -o helloworld.i
EXCHANGE(&a, &b) ————> int tmp = 0; tmp = *&a; *&a = *&b; *&b = tmp;
宏展开后,代码直接成为了main函数的一部分,不存在函数调用的过程,省去了函数的调用开销。
使用宏来实现时可以不使用指针,不过用了也没错。
总之为了效率着想,大家完全可以使用宏来代替简短函数,特别是当程序非常大时,这是很有意义的。
注意我们说的只是简短函数使用宏代替,不要什么函数都使用宏来代替,如果都使用宏来代替的话,会导致程序的代码量急剧上升,代码变大了自然就需要更多的内存空间来存储,这也会带来很大的空间开销。
为什么代码量会急剧上升?
因为所有引用这个宏的地方都会进行宏展开,每个引用宏的地方都会重复包含一份完全相同的代码,程序的代码量自然会急剧上升,所以什么事都不能走极端,走了极端就出麻烦。
2)使用宏来代替简短函数,其实还存在一点小小的缺点
那就是预编译时,宏的参数只是做简单的替换,而不做类型检查,也就是不检查实参类型与形参类型对不对。
为什么宏不做类型检查?
因为宏的形参就没有类型,自然没办法进行类型检查,假如你引用EXCHANGE时,你写成了EXCHANGE(100, 100),此时实参的类型是int,并不是宏体所需要的指针类型,这显然是有问题的,但是预编译时不会进行类型检查,只是简单替换。
但是函数的形参有类型说明,所以编译时会检查函数的实参与形参的类型是否匹配,类型检查其实是很有用的,因为编译时的类型不匹配的提示信息,非常有利于我们排查编译错误。
宏只做替换,不做类型检查,函数会做类型检查,但是不做替换(函数只能调用),为了将二者的特点融合下,后来就有了“内联函数”,内联函数的特点是
(a)有函数的特性:内联函数的形参有类型,会进行类型检查
(b)有宏的特点:内联函数和宏一样,也是一个替换的过程,不存在函数调用
说白了内联函数就是一个宏和函数的特点相综合后的产物,所以对于简短函数来说,最好的方式是使用内联函数来实现。
在Linux内核源码中,会经常看见内联函数这个东西,因为Linux内核必须考虑效率问题,所以几乎所有会被频繁调用的简短函数,都使用内联函数来实现,不过有关内联函数这个东西,我们讲函数时在详细介绍。
实际上你只要注意不要把参数类型弄错了,使用带参宏来替代简短函数,其实也是很好的,在很多源码中,经常会看到这样的用法,因为内联函数也有一个问题,那就是它依赖于编译器的支持,因为内联函数相对来说算是一个比较新的C语法特性,有些老旧的编译器不一定支持,但是你使用带参宏肯定是没问题的,因为宏是一个老的C语法特性。
不过内联函数的出现也有好些年了,所以现在的编译器应该几乎都支持了。