文章目录
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