宏——高级

复杂C源码中,往往可以经常看到各种宏的比较深入的用法,所以我们这里举一些宏的比较高级的、比较深入的用法的例子。

使用宏来代替简短函数

参考:C++——引用

例子

#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
", a, b);

    return 0;
}
View Code

例子中exchange函数就是一个简短函数。

如何判断一个函数是不是简短函数呢?

①代码只有1~5行左右

②函数中没有循环

因为如果有循环的话,也相当于有很多的代码,不过如果你的循环要是非常短的话,比如只循环3~4次,累计的代码量也就只有5行左右的话,也算是简短函数,不过一般来说,我们并不把有循环的函数算作是就简短函数。

简短函数缺点:调用开销比较大

①时间开销:调用时跳转到被调函数处执行,函数执行完毕后,返回到调用处,这些都是需要时间的

②空间开销:调用函数时,往往需要在栈中为形参开辟空间,所以有空间开销。而且开辟和释放形参的空间,也是需要时间的,也有时间开销。

所以对于简短函数来说,函数调用的开销甚至都有可能 > 那1~5行代码的运行开销,所以说如果你在程序中有大量的简短函数的话,会非常影响你的程序质量,特别是当这个简单函数会被频繁调用时,累积的开销就更大了,所以这个时候就可以使用“带参宏”来代替了。

使用带参宏来代替简短函数

exchange.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
", a, b);

    return 0;
}        
View Code

 查看预编译结果

gcc -E exchange.c -o exchange.i

 1 int main(void)
 2 {
 3     int a = 10;
 4     int b = 30;
 5 
 6     int tmp = 0; tmp = *&a; *&a = *&b; *&b = tmp;;
 7 
 8     printf("a=%d, b=%d
", a, b);
 9 
10     return 0;
11 }
View Code

宏展开后,代码直接成为了main函数的一部分,不存在函数调用的过程,省去了函数的调用开销。

使用宏来实现时可以不使用指针,不过用了也没错。

总之为了效率着想,完全可以使用宏来代替简短函数,特别是当程序非常大时,这是很有意义的。

注意:这里说的只是简短函数使用宏代替,不要什么函数都使用宏来代替,如果都使用宏来代替的话,会导致程序的代码量急剧上升,代码变大了自然就需要更多的内存空间来存储,这也会带来很大的空间开销。

为什么代码量会急剧上升?

因为所有引用这个宏的地方都会进行宏展开,每个引用宏的地方都会重复包含一份完全相同的代码,程序的代码量自然会急剧上升,所以什么事都不能走极端,走了极端就出麻烦。

使用宏来代替简短函数,其实还存在一点小小的缺点

那就是预编译时,宏的参数只是做简单的替换,而不做类型检查,也就是不检查实参类型与形参类型对不对。

为什么宏不做类型检查?

因为宏的形参就没有类型,自然没办法进行类型检查,假如你引用EXCHANGE时,你写成了EXCHANGE(100, 100),此时实参的类型是int,并不是宏体所需要的指针类型,这显然是有问题的(见下面代码),但是预编译时不会进行类型检查,只是简单替换。

 1 int main(void)
 2 {
 3     int a = 10;
 4     int b = 30;
 5 
 6     int tmp = 0; tmp = *100; *100 = *200; *200 = tmp;;
 7 
 8     printf("a=%d, b=%d
", a, b);
 9 
10     return 0;
11 }
View Code

而函数的形参有类型说明,所以编译时会检查函数的实参与形参的类型是否匹配,类型检查其实是很有用的,因为编译时的类型不匹配的提示信息,非常有利于我们排查编译错误。

内联函数

宏只做替换,不做类型检查,函数会做类型检查,但是不做替换(函数只能调用),为了将二者的特点融合下,后来就有了“内联函数”,内联函数的特点是

①有函数的特性:内联函数的形参有类型,会进行类型检查

②有宏的特点:内联函数和宏一样,也是一个替换的过程,不存在函数调用

说白了内联函数就是一个宏和函数的特点相综合后的产物,所以对于简短函数来说,最好的方式是使用内联函数来实现。在Linux内核源码中,会经常看见内联函数这个东西,因为Linux内核必须考虑效率问题,所以几乎所有会被频繁调用的简短函数,都使用内联函数来实现。但是内联函数也有问题那就是它依赖于编译器的支持,因为内联函数相对来说算是一个比较新的C语法特性,有些老旧的编译器不一定支持,但是你使用带参宏肯定是没问题的,因为宏是一个老的C语法特性(只要注意不要把参数类型弄错了)。

使用宏来减少函数定义,简化函数调用

recode_lock.h

 1 #ifndef ___RECODE_LOCK__
 2 #define ___RECODE_LOCK__
 3 
 4 #include <unistd.h>
 5 #include <fcntl.h>
 6 #include <stdlib.h>
 7 
 8 
 9 /* 加非阻塞读锁 */
10 #define read_lock(fd, l_whence, l_start, l_len) 
11     lock_set(fd, F_SETLK, F_RDLCK, l_whence, l_start, l_len)
12 
13 /* 加阻塞读锁 */
14 #define read_lockw(fd, l_whence, l_start, l_len) 
15     lock_set(fd, F_SETLKW, F_RDLCK, l_whence, l_start, l_len)
16 
17 /* 加非阻塞写锁 */
18 #define write_lock(fd, l_whence, l_start, l_len) 
19     lock_set(fd, F_SETLK, F_WRLCK, l_whence, l_start, l_len)
20 
21 /* 加阻塞写锁 */
22 #define write_lockw(fd, l_whence, l_start, l_len) 
23     lock_set(fd, F_SETLKW, F_WRLCK, l_whence, l_start, l_len)
24 
25 /* 解锁 */
26 #define unlock(fd, l_whence, l_start, l_len) 
27     lock_set(fd, F_SETLK, F_UNLCK, l_whence, l_start, l_len)
28 
29     
30 /* 操作锁的函数 */
31 static int lock_set(int fd, int l_ifwset, short l_type, short l_whence, 
32     off_t l_start, off_t l_len)
33 {
34     int ret = -1;
35     struct flock f_lock;
36     
37     f_lock.l_type = l_type;
38     f_lock.l_whence = l_whence;
39     f_lock.l_start = l_start;
40     f_lock.l_len = l_len;
41     
42     ret = fcntl(fd, l_ifwset, &f_lock);//加锁解锁
43     
44     return(ret);
45 }
46 
47 #endif
View Code

helloworld.c 

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include "recode_lock.h"
 4 
 5 int main(void)
 6 {    
 7     int ret = 0, fd = -1;
 8         
 9     fd = open("./file", O_CREAT|O_RDWR|O_APPEND|O_TRUNC, 0777);
10     if(fd < 0)
11     {    
12         perror("open is fail");
13         exit(-1);
14     }    
15     
16     while(1)
17     {
18         //加阻塞读锁,也就是加锁失败,程序会休眠在这里,就像scanf没有数据,也会休眠一样
19         read_lockw(fd, SEEK_SET, 0, 0); 
20         
21         write(fd, "hello ", 6);
22         write(fd, "world
", 6);
23         
24         unlock(fd, SEEK_SET, 0, 0); //解锁
25     }    
26 }        
View Code

使用宏对了类型进行自定义命名

1 #define INT32_t int
2     INT32 a;
3 
4 #define U32_t   usigned int 
5     U32_t a;
6     
7 #define STUDENT_t struct info_student; 
8     STUDENT stu;
View Code

不过对类型自定义命名,最好还是使用typedef来实现,因为宏只是简单的替换,如果使用不当的话,这种简单替换会导致bug,有关这个问题,我们后面讲typedef时再来对比介绍。不过使用宏这种方式来实现类型自定义命名方式,冷不丁的在有些源码中还是会看见的,特别是在好些单片机的程序中,这种方式还是挺多的。

C++中,使用typedef类型定义,只是定义了一个类型别名”,而不是一个新的类型。

原文地址:https://www.cnblogs.com/kelamoyujuzhen/p/9478536.html