宏替代和内联函数的用法

文章探讨了宏替代在提高代码可读性和执行效率上的作用,特别是在简化简短函数调用时的优势。同时指出了宏替代的缺点,如无参数检查和可能导致代码膨胀。内联函数作为折衷方案,结合了宏和函数的优点,但在某些情况下需要编译器支持。文章还强调了使用宏时需注意括号的正确使用,以避免运算优先级问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、宏替代的优点

1.1、提高代码的可读性和可维护性

例子: #define PI 3.14 ,代码中用到3.14的地方都可以用PI来替代。当要修改PI的精度时,也不需要把代码中的每个3.14 改成3.1415,而是直接 #define PI 3.1415,就行。

1.2、提高代码的执行效率

当使用宏来替代简短函数时,可以提高代码的执行效率。

1.2.1、什么是简短函数
   1、代码只有1~5行
   2、函数中没有循环
1.2.2、简短函数的缺点
  调用开销比较大   
   
  1、时间开销:调用时跳转到被调函数处执行,函数执行完毕后,返回到调用处,这些都是需要时间的
  2、空间开销:调用函数时,往往需要在栈中为形参开辟空间,所以有空间开销,而且开辟和释放形参的空间,也是需要时间的,也有时间开销。     

所以对于简短函数来说,函数调用的开销甚至都有可能大于那1~5行代码的运行开销,特别是当这个简短函数会被频繁调用时,累积的开销就更大了,所以这个时候就可以使用“带参宏”来替代了。例如:

//简短函数                   
void exchange(int *p1, int *p2)
{
    int tmp = 0;
    tmp = *p1;
    *p1 = *p2;
    *p2 = tmp;
}
//带参宏替代函数
#define EXCHANGE(p1, p2) \
         int tmp = 0;\
         tmp = p1;\
         p1 = p2;\
         p2 = tmp;\

我们写一个main函数看下调用带参宏和函数的区别:

int main(void)
{
  int a = 10;
  int b = 30;
  
  EXCHANGE(a, b);
  exchange(&a, &b);
  while(1){}
}

看下上面代码生成的汇编:

    32: int main(void) 
0x08000182 4770      BX       lr
    33: { 
    34:  
0x08000184 B50C      PUSH     {r2-r3,lr}
    35:   int a = 10; 
0x08000186 200A      MOVS     r0,#0x0A
0x08000188 9001      STR      r0,[sp,#0x04]
    36:   int b = 30; 
    37:    
0x0800018A 201E      MOVS     r0,#0x1E
0x0800018C 9000      STR      r0,[sp,#0x00]
    38:   EXCHANGE(a, b); 
0x0800018E 2400      MOVS     r4,#0x00
0x08000190 9C01      LDR      r4,[sp,#0x04]
0x08000192 9800      LDR      r0,[sp,#0x00]
0x08000194 9001      STR      r0,[sp,#0x04]
0x08000196 9400      STR      r4,[sp,#0x00]
    39:   exchange(&a, &b); 
    40:    
0x08000198 4669      MOV      r1,sp
0x0800019A A801      ADD      r0,sp,#0x04
0x0800019C F7FFFFEC  BL.W     exchange (0x08000178)
    41:         while(1) 
0x080001A0 BF00      NOP      

可以看出当使用带参宏来替换简短函数exchange()时,直接用的是替换,没有像调用简短函数那样需要跳转( BL.W exchange (0x08000178)),提高了代码的执行效率。

2、宏替代的缺点

1、只做替换,不做入参检查,(在编译的预处理阶段就替换)
2、长函数不能做替代,如果替代了,调用的地方多的话,代码量会急剧增加。

3、宏替代函数和函数调用的区别

宏: 只做替换,不做类型检查,执行效率高;
函数:会做类型检查,但是不做替换(函数只能调用),而调用时,时间和空间的开销大效率相对低

为了将二者的特点融合下,后来就有了“内联函数”,内联函数的特点是:

1、有函数的特性:内联函数有形参,会进行类型检查
2、有宏的特点:内联函数和宏一样,也是替换,不是函数调用;

说白了内联函数就是一个宏和函数的特点相综合后的产物,所以对于简短函数来说,最好的方式是使用内联函数来实现但,内联函数的缺点是:需要编译器支持。例子:

//内联函数
inline void exchange2(int *p1, int *p2)
{
    int tmp = 0;
    tmp = *p1;
    *p1 = *p2;
    *p2 = tmp;
}

调用时生成的汇编代码:

    39:   exchange(&a, &b); 
0x08000198 4669      MOV      r1,sp
0x0800019A A801      ADD      r0,sp,#0x04
0x0800019C F7FFFFEC  BL.W     exchange (0x08000178)
    40:   exchange2(&a, &b); 
    41:    
0x080001A0 BF00      NOP      
    26:     int tmp = 0; 
0x080001A2 2000      MOVS     r0,#0x00
    27:     tmp = *p1; 
0x080001A4 9801      LDR      r0,[sp,#0x04]
    28:     *p1 = *p2; 
0x080001A6 9900      LDR      r1,[sp,#0x00]
0x080001A8 9101      STR      r1,[sp,#0x04]
    29:     *p2 = tmp; 
0x080001AA 9000      STR      r0,[sp,#0x00]

4、使用宏需要注意的几点

1、宏定义为#define ,不带分号
2、预处理器将会把宏函数中的常量表达式计算成其中结果,然后进行替代。
3、宏定义的整体和每一个参数都要用括弧

解释下第2点:假设有这样一个宏函数,且有如下的宏调用

#define Q15(Float_Value) \
(((Float_Value) < 0.0) ? (s32)(32768 * (Float_Value) - 0.5): (s32)(32767 * (Float_Value) + 0.5)) 

 a = Q15(2.0/4.0);    
 b = Q15(sqrt(4)/4.0);

对于 a = Q 15 ( 2.0 / 4.0 ) ; a = Q15(2.0/4.0); a=Q15(2.0/4.0); 预处理器会将其展开为 ((s32)(32767 * (2.0/4.0) + 0.5)),然后计算出结果并替代宏调用的位置。
而对于 b = Q 15 ( s q r t ( 4 ) / 4.0 ) ; b = Q15(sqrt(4)/4.0); b=Q15(sqrt(4)/4.0);这样的宏调用,预处理器将无法直接计算平方根。预处理器只能处理简单的宏替换和基本的预处理指令。
看下生成的汇编代码:

    44:         while(1) 
    45:         { 
0x08000DA0 E040      B        0x08000E24
    46:     a = Q15(2.0/4.0); //预处理器会将其展开为 ((s32)(32767 * (2.0/4.0) + 0.5)),然后计算出结果并替代宏调用的位置。 
    47:      
0x08000DA2 F44F4080  MOV      r0,#0x4000
0x08000DA6 9002      STR      r0,[sp,#0x08]
    48:     b = Q15(sqrt(4)/4.0); //这样的宏调用,预处理器将无法直接计算平方根。预处理器只能处理简单的宏替换和基本的预处理指令 
0x08000DA8 2000      MOVS     r0,#0x00
0x08000DAA 4922      LDR      r1,[pc,#136]  ; @0x08000E34
0x08000DAC F000F848  BL.W     sqrt (0x08000E40)
0x08000DB0 4606      MOV      r6,r0
0x08000DB2 2200      MOVS     r2,#0x00
0x08000DB4 4B1F      LDR      r3,[pc,#124]  ; @0x08000E34
0x08000DB6 F7FFFAE5  BL.W     __aeabi_ddiv (0x08000384)
0x08000DBA 4604      MOV      r4,r0
0x08000DBC 2200      MOVS     r2,#0x00
0x08000DBE 4613      MOV      r3,r2
0x08000DC0 F7FFFB6E  BL.W     __aeabi_cdcmpeq (0x080004A0)
0x08000DC4 D216      BCS      0x08000DF4
0x08000DC6 2000      MOVS     r0,#0x00
0x08000DC8 491A      LDR      r1,[pc,#104]  ; @0x08000E34
0x08000DCA F000F839  BL.W     sqrt (0x08000E40)
0x08000DCE 4682      MOV      r10,r0
0x08000DD0 2200      MOVS     r2,#0x00

解释下第3点:因为宏定义中无论是整体还是参数都是在程序中直接展开(常数表达式计算出值后展开),所以必须加括弧。

例如参数不加括号:

#define SQRT(n) n x n就会有问题,比如带入一个表达式:SQRT(3+2);展开就是3+2 x 3+2,并非我们需要的结果.。所以应该是 #define SQRT(n) (n) x(n) 。

例如整体不加括号:

#define ADD(m,n) (m)+(n) ,然后计算ADD(3,2)*3的值,(3)+(2)*3;并非我们需要的结果。
所以应该是 #define ADD(m,n) ((m)+(n))

综合以上,宏的无论是参数还是整体定义都要用括弧小心的括起来。这样能保证展开还是一个整体而不受外部优先级更高的运算符干扰。

总结:宏替代和内联函数都是为了提高代码的执行效率,当然还可以把相应的函数放在RAM中执行。
可以参考:https://www.jianshu.com/p/4e3d40b8b4ab

转载链接:宏深入用法——使用宏来代替简短函数
参考文章:C++之宏定义
文章代码:https://download.youkuaiyun.com/download/wanrenqi/87504421

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值