经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C 语言 » 查看文章
C语言中带返回值的宏定义方式
来源:jb51  时间:2023/2/27 9:41:46  对本文有异议

C语言中带返回值的宏定义

相信大家在实际工作中,一定有遇到需要编写一个宏定义,且希望它能带返回值的场景吧?

比如我之前就遇到一个场景,早期的代码是使用函数实现的功能,现在想换成宏定义,但是又要保留之前调用函数的代码不动,这样我就只能想办法写一个带返回值的宏了。

宏定义编写

直接上demo:

  1. #include <stdio.h>
  2.  
  3. /* always return 1 */
  4. #define RETURN_MACRO()?? ??? ??? ?({do {} while(0);1;})
  5. #define RETURN_MACRO2()?? ??? ??? ?1
  6.  
  7. /* return a+b */
  8. #define A_PLUS_B_MACRO(a, b)?? ?({int ret; ret = (a) + (b); ret;})
  9. #define A_PLUS_B_MACRO2(a, b)?? ?({int ret; ret = add((a), (b)); ret;})
  10.  
  11. int add(int a, int b)
  12. {
  13. ?? ?return (a + b);
  14. }
  15.  
  16. int main(int argc, const char *argv[])
  17. {
  18. ?? ?int a = 6;
  19. ?? ?int b = 7;
  20.  
  21. ?? ?printf("Hello world !\n");
  22. ?? ?printf("RETURN_MACRO: %d\n", RETURN_MACRO());
  23. ?? ?printf("RETURN_MACRO2: %d\n", RETURN_MACRO2());
  24. ?? ?printf("a + b = %d\n", A_PLUS_B_MACRO(a, b));
  25. ?? ?printf("a + b = %d\n", A_PLUS_B_MACRO2(a, b));
  26.  
  27. ?? ?return 0;
  28. }

宏定义分析

为了分析宏定义的写法,我们得知道宏定义最终被展开是什么样的。

我在之前的博文中有提到,使用gcc编译器的话,可以在CFLAGS上加上-save-temps=obj这个编译选项,这样就可以得到预编译处理之后的文件,后缀名是.i。

我们使用编译脚本编译之后,得到.i文件如下:

  1. //前面的内容忽略
  2.  
  3. # 3 "main.c" 2
  4. # 12 "main.c"
  5.  
  6. # 12 "main.c"
  7. int add(int a, int b)
  8. {
  9. ?return (a + b);
  10. }
  11.  
  12. int main(int argc, const char *argv[])
  13. {
  14. ?int a = 6;
  15. ?int b = 7;
  16.  
  17. ?printf("Hello world !\n");
  18. ?printf("RETURN_MACRO: %d\n", ({do {} while(0);1;}));
  19. ?printf("RETURN_MACRO2: %d\n", 1);
  20. ?printf("a + b = %d\n", ({int ret; ret = (a) + (b); ret;}));
  21. ?printf("a + b = %d\n", ({int ret; ret = add((a), (b)); ret;}));
  22.  
  23. ?return 0;
  24. }

从.i文件我们可以看到,宏定义被正常展开,下面确认下功能是否正常。

宏定义验证

我们执行编译出来的可执行文件:

  1. return_macro$ ./test?
  2. Hello world !
  3. RETURN_MACRO: 1
  4. RETURN_MACRO2: 1
  5. a + b = 13
  6. a + b = 13

验证成功。

经验总结

  • 在C语言里面,可以使用({aaa; bbb; ccc;})来实现宏定义带返回值;这里的返回值是最后一个;的值。
  • 注意里面的()和{}都不能少,否则可能会破坏代码的语法结构,导致得不到正确的答案。

C语言中一些宏定义和常用的函数

typeof 关键字

如果你是 C++ 程序员,应该接触过 C++11 里的 decltype 操作符,它的作用是自动推导表达式的数据类型,以解决泛型编程中有些类型由模板参数决定而难以(甚至不可能)表示的问题。

其实这个特性在 C 语言中也早有类似的实现,GNU C 标准中的一个扩展特性 typeof 作用与 decltype 类似。

  1. __typeof__ (ret) errnum = (ret);?

snprintf()函数的作用

  1. #include<stdio.h>
  2. int snprintf(char* dest_str,size_t size,const char* format,...);

【函数功能】:

先将可变参数 “…” 按照format的格式格式化为字符串,然后再将其拷贝至dest_str中。

如果格式化后的字符串长度小于size,则将字符串全部拷贝至dest_str中,并在字符串结尾处加上‘\0’; 如果格式化后的字符串长度大于或等于size,则将字符串的(size-1)拷贝至dest_str中,然后在字符串结尾处加上’\0’. 函数返回值是 格式化字符串的长度。

__builtin_expect的作用

  1. __builtin_expect(errnum != 0, 0)

这个指令是gcc引入的,作用是"允许程序员将最有可能执行的分支告诉编译器"。

这个指令的写法为:__builtin_expect(EXP, N)。意思是:EXP==N的概率很大。

一般的使用方法是将__builtin_expect指令封装为LIKELY和UNLIKELY宏。

C语言中常用的预定义

  • __LINE__:当前程序行的行号,表示为十进制整型常量
  • __FILE__:当前源文件名,表示字符串型常量
  • __DATE__:转换的日历日期,表示为Mmm dd yyyy 形式的字符串常量,Mmm是由asctime产生的。
  • __TIME__:转换的时间,表示"hh:mm:ss"形式的字符串型常量,是有asctime产生的。(asctime貌似是指的一个函数)
  • __STDC__:编辑器为ISO兼容实现时位十进制整型常量
  • __func__:它指示所在的函数
  • __assert_perror_fail:打印一条包含错误码ERRNUM的错误消息,并终止程序

反斜杠的作用

反斜杠起到换行作用,用于宏定义和字符串换行。其中宏定义中使用居多。

如果一行代码有很多元素,导致太长影响阅读,可以通过在结尾加\的方式,实现换行,编译时会忽略\及其后的换行符,当做一行处理。

  1. #define CHECK_ACTION_RETURN(expr) ? ? if (!expr) { ? ? ? ? printf(":failed(%d)\n", ret); ? ? ? ? return ret; ? ? ? ? ? ? ? ? } else { ? ? ? ? printf(":ok\n"); ? ? ? ? ? ? ? ? }

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持w3xue。

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号