首页 /  技术专区  /  C 宽屏模式 >

宏深入用法——使用宏来代替简短函数

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语法特性。

不过内联函数的出现也有好些年了,所以现在的编译器应该几乎都支持了。



头像
0/200
图片验证码