zoukankan      html  css  js  c++  java
  • 宏——高级

    复杂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类型定义,只是定义了一个类型别名”,而不是一个新的类型。

  • 相关阅读:
    一目了然卷积神经网络
    浅析 MVC Pattern
    程序员读书清单
    “数据中台”的再思考
    火锅、报表与数据库的故事
    从NoSQL到NewSQL,谈交易型分布式数据库建设要点
    从架构特点到功能缺陷,重新认识分析型分布式数据库
    YCSB测试HBase远程完全分布式集群
    NoSQL性能测试工具YCSB-Running a Workload
    再谈数据库事务隔离性
  • 原文地址:https://www.cnblogs.com/kelamoyujuzhen/p/9478536.html
Copyright © 2011-2022 走看看