GDB调试技巧:调试复杂的宏定义

简介: 作者:gfree.wind@gmail.com 博客:blog.focus-linux.net   linuxfocus.blog.chinaunix.net  本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。
作者:gfree.wind@gmail.com
博客:blog.focus-linux.net   linuxfocus.blog.chinaunix.net
 
 
本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
======================================================================================================

C语言中的宏定义,有着各种各样的好处和坏处,可谓让人有爱有恨。在大型的工程项目中,为了简洁,为了封装,宏的应用必不可少。但是在调试问题时,因为宏定义是被预定义处理的,所以不会有任何的编译符号和调试信息。这样给调试宏定义时,带来了很大的困难。对于开发人员来说,除了直接肉眼去看宏定义,自己来展开宏定义去确定问题,是否还有其它手段来调试宏定义吗?

本文介绍两种调试宏定义的小技巧:

第一个方法是通过gcc -E 产生预编译后的源代码,即源代码经过预编译后的结果,所有的预编译动作都已完成。如头文件的插入,宏定义的展开。
如下面的代码:
  1. #include stdlib.h>
  2. #include stdio.h>

  3. #define MACRO1(x) (++(x))
  4. #define MACRO2(x) (MACRO1(x)+100)
  5. #define MACRO3(x) (MACRO2(x)+200)


  6. int main(void)
  7. {
  8.     int a = 0;
  9.     int b = 0;

  10.     b = MACRO3(a);

  11.     printf("%d\n", b);

  12.     return 0;
  13. }
这里的MACRO3嵌套调用了MACRO2,MACRO1。在真正的代码中,这种用法很常见,不过这处的宏定义很简单,即使是嵌套调用也很容易看出。此处只是一个示意。
Ok,使用gcc -E test.c > test.e,得到预编译后的代码:
  1. /*
  2. 前面是1800+行的头文件代码,此处省略
  3. */

  4. int main(void)
  5. {
  6.     int a = 0;
  7.     int b = 0;

  8.     b = (((++(a))+100)+200);

  9.     printf("%d\n", b);

  10.     return 0;
  11. }
这里可以清晰的看到b = (((++(a))+100)+200);这个就比刚才的宏定义要清楚的多。

但是从这个例子也可以看到这个方法的局限性。
1. 由于预编译处理会执行所有的预处理代码,包括头文件的插入,这导致最后的代码行数太多。
2. 得到的了一个新的代码文件。这样的话,在大型工程中,如果需要调试多个文件中的宏定义,需要我们一个一个的预编译,太麻烦了。


下面看看第二个方法,这个方法要比第一种方法方便得多。
我们都知道为了调试程序,需要使用-g选项,它的作用就是将调试信息加入到最后的二进制可执行文件中。但是你可知道-g 也通-o一样,是分级别的。当不指定级别的时候,其level为2。为了调试宏定义,我们可以使用更高的级别-g3。
下面为我使用-g3编译上面的代码,然后进行调试:
  1. Breakpoint 1, main () at test.c:11
  2. 11 int a = 0;
  3. Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.i686
  4. (gdb) n
  5. 12 int b = 0;
  6. (gdb)
  7. 14 b = MACRO3(a);
  8. (gdb)
  9. 16 printf("%d\n", b);
  10. (gdb) macro expand MACRO3(a)
  11. expands to: (((++(a))+100)+200)
  12. (gdb) macro expand MACRO3(0)
  13. expands to: (((++(0))+100)+200)
  14. (gdb) macro exp MACRO3(0)
  15. expands to: (((++(0))+100)+200)
  16. (gdb)
在调试的过程中,可以使用macro expand/exp 来展开宏定义。从上面的调试过程中,可以直接看到宏定义展开后的结果。并且我们还可以给宏传入任何的一个值,如:
  1. (gdb) macro exp MACRO3(3)
  2. expands to: (((++(3))+100)+200)
  3. (gdb)

第二个方法无疑比第一个方法要方便简单得多。我们只需要在全局的Makefile中添加新的编译参数-g3,就可以支持整个工程代码中所有的宏的调试。当然这个方法也有一个缺点,就是g3的调试信息会比默认的g2的调试信息要大——自然嘛,不然gdb如何知道怎样展开宏定义呢。


相关实践学习
阿里云图数据库GDB入门与应用
图数据库(Graph Database,简称GDB)是一种支持Property Graph图模型、用于处理高度连接数据查询与存储的实时、可靠的在线数据库服务。它支持Apache TinkerPop Gremlin查询语言,可以帮您快速构建基于高度连接的数据集的应用程序。GDB非常适合社交网络、欺诈检测、推荐引擎、实时图谱、网络/IT运营这类高度互连数据集的场景。 GDB由阿里云自主研发,具备如下优势: 标准图查询语言:支持属性图,高度兼容Gremlin图查询语言。 高度优化的自研引擎:高度优化的自研图计算层和存储层,云盘多副本保障数据超高可靠,支持ACID事务。 服务高可用:支持高可用实例,节点故障迅速转移,保障业务连续性。 易运维:提供备份恢复、自动升级、监控告警、故障切换等丰富的运维功能,大幅降低运维成本。 产品主页:https://www.aliyun.com/product/gdb
目录
相关文章
|
9月前
|
编译器 程序员 C++
VS编译器实用调试技巧
VS编译器实用调试技巧
|
Linux C语言
介绍几种LINUX编程中非常实用的调试程序宏变量
介绍几种LINUX编程中非常实用的调试程序宏变量
|
Linux iOS开发
查看宏展开后的代码
查看宏展开后的代码
520 0
|
关系型数据库 PostgreSQL
PostgreSQL ring buffer策略
PostgreSQL ring buffer策略 When running a query that needs to access a large number of pages just once,such as VACUUM or a large sequential scan, a different strategy is used.
1903 0
|
NoSQL
GDB调试
gdb详细信息
693 0
|
NoSQL Linux C语言
Linux环境下段错误的产生原因及调试方法小结
最近在Linux环境下做C语言项目,由于是在一个原有项目基础之上进行二次开发,而且项目工程庞大复杂,出现了不少问题,其中遇到最多、花费时间最长的问题就是著名的“段错误”(Segmentation Fault)。
1045 0