你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

特殊的函数宏封装方式

[复制链接]
gaosmile 发布时间:2020-11-10 21:40

1
函数宏介绍函数宏,即包含多条语句的宏定义,其通常为某一被频繁调用的功能的语句封装,且不想通过函数方式封装来降低额外的弹栈压栈开销。函数宏本质上为宏,可以直接进行定义,例如:#define INT_SWAP(a,b) \
    int tmp = a;    \
    a = b;          \
    b = tmp
但上述的宏具有一个明显的缺点:当遇到 if、while 等语句且不使用花括号仅调用宏时,实际作用范围在宏的第一个分号后便结束。即 a = b 和 b = tmp 均不受控制语句所作用。因此,在工程中,一般使用三种方式来对函数宏进行封装,分别为 {}、do{...}while(0) 和({})。下文将一一对三种方式进行分析,比较各自的优劣点。

2
{} 方式
INT_SWAP 宏使用 {} 封装后形态如下:
#define INT_SWAP(a,b)\
{                   \
    int tmp = a;    \
    a = b;          \
    b = tmp;        \
}
此时,直接调用与在无花括号的控制语句(如 if、while)中调用均能正常运行,例如:
#define INT_SWAP(a,b) \
{                   \
    int tmp = a;    \
    a = b;          \
    b = tmp;        \
}

int main()
{
int var_a = 1;
int var_b = 2;

INT_SWAP(var_a, var_b);
printf("var_a = %d, var_b = %d\n", var_a, var_b);   // var_a = 2, var_b = 1

if (1)
    INT_SWAP(var_a, var_b);
printf("var_a = %d, var_b = %d\n", var_a, var_b);   // var_a = 1, var_b = 2
}
但当无花括号的 if 语句存在其他分支(else if、else 等)如:
if (1)
   INT_SWAP(var_a, var_b);
else
printf("hello world!\n");
会发现编译出错:
...
/mnt/hgfs/share/pr_c/src/main.c: In function ‘main’:
/mnt/hgfs/share/pr_c/src/main.c:18:2: error: ‘else’ without a previous ‘if’
  else
这是因为 INT_SWAP(var_a, var_b); 最后的 ; 已经把 if 的作用域终结了,后续的 else 当然没有找到与之匹配的 if 了。因此,解决方法有两种,分别为不使用 ;(port.1)或规定必须使用带花括号的if(port.2),例如:/* port.1 */
if (1)
   INT_SWAP(var_a, var_b)
else
{
    printf("hello world!\n");
}

/* port.2 */
if (1)
{
   INT_SWAP(var_a, var_b);
}
else
{
    printf("hello world!\n");
}
可见,不使用 ; 的调用方式无论从程序阅读还是使用方法方面都是十分别扭的;而规定必须使用带花括号的 if 的调用方式有违常理的,因为宏函数应该适用于任何语法。
优缺点总结:
  • 优点:简单粗暴。
  • 缺点:不能在无花括号且有分支的 if 语句中直接调用;能够不带 ; 直接调用。

3
do{...}while(0) 方式
INT_SWAP 宏使用 do{...}while(0) 封装后形态如下:
#define INT_SWAP(a,b)   \
do{                     \
    int tmp = a;        \
    a = b;              \
    b = tmp;            \
}while(0)
do{...}while(0) 表示只执行一遍 {} 内的语句,表象来说与 {} 的功能是一致的。不同的是,do{...}while(0) 可以提前退出函数宏、整合为一条语句与强制调用时必须使用 ;。
由于 do{...}while(0) 实际为 while 循环,因此可以使用关键字 break 提前结束循环。利用该特性,可以为函数宏添加参数检测。例如:
#define INT_SWAP(a,b)  \
do{                 \
if (a < 0 || b < 0) \
  break;   \
    int tmp = a;     \
    a = b;           \
    b = tmp;         \
}while(0)
由于 do{...}while(0); 实际为一种语法,编译器会把 do{...}while(0); 认为为一条语句。
因此,do{...}while(0) 方式的函数宏可以在无花括号且有分支的 if 语句中直接调用。例如:
#define INT_SWAP(a,b)  \
do{                 \
if (a < 0 || b < 0) \
  break;   \
    int tmp = a;     \
    a = b;           \
    b = tmp;         \
}while(0)

int main()
{
int var_a = 1;
int var_b = 2;

if (1)
    INT_SWAP(var_a, var_b);
else
  printf("hello world!\n");
printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 1

return 0;
}

C 语言规定,do{...}while(0) 语法必须使用 ; 作为语句结尾。因此不可能存在以下语句的程序出现:
if (1)
   INT_SWAP(var_a, var_b)
else
{
printf("hello world!\n");
}

优缺点总结:
  • 优点:支持在无花括号且有分支的 if 语句中直接调用;支持提前退出函数宏;强制调用时必须使用 ;。
  • 缺点:无返回值,不能作为表达式的右值使用。
4
({}) 方式
({}) 为 GNU C 扩展的语法,非 C 语言的原生语法。
INT_SWAP 宏使用 ({}) 封装后形态如下:
#define INT_SWAP(a,b)   \
({                      \
    int tmp = a;        \
    a = b;              \
    b = tmp;            \
})

与 do{...}while(0) 相同,({}) 支持在无花括号且有分支的 if 语句中直接调用。例如:
#define INT_SWAP(a,b)  \
({                 \
int tmp = a;    \
a = b;          \
b = tmp;        \
})

int main()
{
int var_a = 1;
int var_b = 2;

if (1)
    INT_SWAP(var_a, var_b);
else
  printf("hello world!\n");
printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 1

return 0;
}

与 do{...}while(0) 不同的是,({}) 不能提前退出函数宏与支持返回值。({}) 毕竟不是 while 循环,不能直接使用 break退出函数宏是比较容易理解。那支持返回值是什么意思呢?
答案是 C 语言规定 ({}) 中的最后一条语句的结果为该双括号体的返回值。例如:
int main()
{
int a = ({
  10;
  1000;
});
printf("a = %d\n", a);      // a = 1000
}

因此,({}) 可以为函数宏提供返回值。例如:
#define INT_SWAP(a,b)  \
({                 \
int ret = 0;  \
if (a < 0 || b < 0) \
{     \
  ret = -1;  \
}     \
else    \
{     \
  int tmp = a;    \
  a = b;          \
  b = tmp;        \
}     \
ret;    \
})

int main()
{
int var_a = 1;
int var_b = 2;

if (INT_SWAP(var_a, var_b) != -1)
  printf("swap success !!\n");     // swap success !!
else
  printf("swap fail !!\n");
printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 1

return 0;
}
可见,此时的 INT_SWAP 宏已与函数十分接近。
优缺点总结:
  • 优点:支持在无花括号且有分支的 if 语句中直接调用;有返回值,支持作为表达式的右值。
  • 缺点:不支持提前退出函数宏;非 C 的原生语法,编译器可能不支持。

5
总结
综上,在 {}、do{...}while(0) 和 ({}) 这三种函数宏的封装方式之中,应尽可能不使用 {},考虑兼容性一般选择使用 do{...}while(0),当需要函数宏返回时可以考虑使用 ({}) 或直接定义函数。


收藏 评论0 发布时间:2020-11-10 21:40

举报

0个回答

所属标签

STM32团队

意法半导体微控制器和微处理器拥有广泛的产品线,包含低成本的8位单片机和基于ARM® Cortex®-M0、M0+、M3、M4、M33、M7及A7内核并具备丰富外设选择的32位微控制器及微处理器


最新内容

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
微信公众号二维码 微信公众号
手机版二维码 手机版