9条必学的嵌入式C语言调试技巧!宏定义的妙用

一口Linux 2025-01-27 10:00

击左上方蓝色“一口Linux”,选择“设为星标

第一时间看干货文章 

【干货】嵌入式驱动工程师学习路线
【干货】Linux嵌入式知识点-思维导图-免费获取
【就业】一个可以写到简历的基于Linux物联网综合项目
【就业】找工作简历模版




01.调试相关的宏

在Linux使用gcc编译程序的时候,对于调试的语句还具有一些特殊的语法。gcc编译的过程中,会生成一些宏,可以使用这些宏分别打印当前源文件的信息,主要内容是当前的文件、当前运行的函数和当前的程序行。

具体宏如下:

__FILE__  当前程序源文件 (char*)
__FUNCTION__  当前运行的函数 (char*)
__LINE__  当前的函数行 (int)

这些宏不是程序代码定义的,而是有编译器产生的。这些信息都是在编译器处理文件的时候动态产生的。

测试示例:

#include 

int main(void)
{
    printf("file: %s\n", __FILE__);
    printf("function: %s\n", __FUNCTION__);
    printf("line: %d\n", __LINE__);

    return 0;
}

02.# 字符串化操作符

在gcc的编译系统中,可以使用#将当前的内容转换成字符串。

程序示例:

#include 

#define DPRINT(expr) printf("
%s = %d\n", #expr, expr);

int main(void)
{
    int x = 3;
    int y = 5;

    DPRINT(x / y);
    DPRINT(x + y);
    DPRINT(x * y);
    
    return 0;
}

执行结果:

deng@itcast:~/tmp$ gcc test.c 
deng@itcast:~/tmp$ ./a.out  
x / y = 0
x + y = 8
x * y = 15

#expr表示根据宏中的参数(即表达式的内容),生成一个字符串。该过程同样是有编译器产生的,编译器在编译源文件的时候,如果遇到了类似的宏,会自动根据程序中表达式的内容,生成一个字符串的宏。

这种方式的优点是可以用统一的方法打印表达式的内容,在程序的调试过程中可以方便直观的看到转换字符串之后的表达式。具体的表达式的内容是什么,有编译器自动写入程序中,这样使用相同的宏打印所有表达式的字符串。

//打印字符
#define debugc(expr) printf(" %s = %c\n", #expr, expr)
//打印浮点数
#define debugf(expr) printf(" %s = %f\n", #expr, expr)
//按照16进制打印整数
#define debugx(expr) printf(" %s = 0X%x\n", #expr, expr);

由于#expr本质上市一个表示字符串的宏,因此在程序中也可以不适用%s打印它的内容,而是可以将其直接与其它的字符串连接。因此,上述宏可以等价以下形式:

//打印字符
#define debugc(expr) printf(" #expr = %c\n", expr)
//打印浮点数
#define debugf(expr) printf(" #expr = %f\n", expr)
//按照16进制打印整数
#define debugx(expr) printf(" #expr = 0X%x\n", expr);

总结:

#是C语言预处理阶段的字符串化操作符,可将宏中的内容转换成字符串。

03.## 连接操作符

在gcc的编译系统中,##是C语言中的连接操作符,可以在编译的预处理阶段实现字符串连接的操作。

程序示例:

#include 

#define test(x) test##x

void test1(int a)
{
    printf("test1 a = %d\n", a);
}

void test2(char *s)
{
    printf("test2 s = %s\n", s);
}

int main(void)
{
    test(1)(100);

    test(2)("hello world");
    
    return 0;
}

上述程序中,test(x)宏被定义为test##x, 他表示test字符串和x字符串的连接。

在程序的调试语句中,##常用的方式如下

#define DEBUG(fmt, args...) printf(fmt, ##args)

替换的方式是将参数的两个部分以##连接。##表示连接变量代表前面的参数列表。使用这种形式可以将宏的参数传递给一个参数。args…是宏的参数,表示可变的参数列表,使用##args将其传给printf函数.

总结:

##是C语言预处理阶段的连接操作符,可实现宏参数的连接。

04.调试宏第一种形式

一种定义的方式:

#define DEBUG(fmt, args...)             \
    {                                   \
    printf("file:%s function: %s line: %d ", __FILE__, __FUNCTION__, __LINE__);\
    printf(fmt, ##args);                \
    }

程序示例:

#include 

#define DEBUG(fmt, args...)             \
    {                                   \
    printf("file:%s function: %s line: %d ", __FILE__, __FUNCTION__, __LINE__);\
    printf(fmt, ##args);                \
    }



int main(void)
{
    int a = 100;
    int b = 200;

    char *s = "hello world";
    DEBUG("a = %d b = %d\n", a, b);
    DEBUG("a = %x b = %x\n", a, b);
    DEBUG("s = %s\n", s);
    
    return 0;
}

总结:

上面的DEBUG定义的方式是两条语句的组合,不可能在产生返回值,因此不能使用它的返回值。

05.第二种定义方式

调试宏的第二种定义方式

#define DEBUG(fmt, args...)             \
    printf("file:%s function: %s line: %d "fmt, \
    __FILE__, __FUNCTION__, __LINE__, ##args)

程序示例

#include 

#define DEBUG(fmt, args...)             \
    printf("file:%s function: %s line: %d "fmt, \
    __FILE__, __FUNCTION__, __LINE__, ##args)



int main(void)
{
    int a = 100;
    int b = 200;

    char *s = "hello world";
    DEBUG("a = %d b = %d\n", a, b);
    DEBUG("a = %x b = %x\n", a, b);
    DEBUG("s = %s\n", s);
    
    return 0;
}

总结:

fmt必须是一个字符串,不能使用指针,只有这样才可以实现字符串的功能。

06.分级审查

即使定义了调试的宏,在工程足够大的情况下,也会导致在打开宏开关的时候在终端出现大量的信息。而无法区分哪些是有用的。这个时候就要加入分级检查机制,可以定义不同的调试级别,这样就可以对不同重要程序和不同的模块进行区分,需要调试哪一个模块就可以打开那一个模块的调试级别。

一般可以利用配置文件的方式显示,其实Linux内核也是这么做的,它把调试的等级分成了7个不同重要程度的级别,只有设定某个级别可以显示,对应的调试信息才会打印到终端上。

可以写出一下配置文件

[debug]
debug_level=XXX_MODULE

解析配置文件使用标准的字符串操作库函数就可以获取XXX_MODULE这个数值。

int show_debug(int level)
{
    if (level == XXX_MODULE)
    {
        #define DEBUG(fmt, args...)             \
        printf("file:%s function: %s line: %d "fmt, \
        __FILE__, __FUNCTION__, __LINE__, ##args)       

    }
    else if (...)
    {
        ....
    }
}

07.条件编译

在实际的开发中,一般会维护两种源程序,一种是带有调试语句的调试版本程序,另外一种是不带有调试语句的发布版本程序。然后根据不同的条件编译选项,编译出不同的调试版本和发布版本的程序。

在实现过程中,可以使用一个调试宏来控制调试语句的开关。

#ifdef USE_DEBUG
        #define DEBUG(fmt, args...)             \
        printf("file:%s function: %s line: %d "fmt, \
        __FILE__, __FUNCTION__, __LINE__, ##args)  

#else
  #define DEBUG(fmt, args...)

#endif

如果USE_DEBUG被定义,那么有调试信息,否则DEBUG就为空。

如果需要调试信息,就只需要在程序中更改一行就可以了。

#define USE_DEBUG
#undef USE_DEBUG

定义条件编译的方式使用一个带有值的宏

#if USE_DEBUG
        #define DEBUG(fmt, args...)             \
        printf("file:%s function: %s line: %d "fmt, \
        __FILE__, __FUNCTION__, __LINE__, ##args)  

#else
  #define DEBUG(fmt, args...)

#endif

可以使用如下方式进行条件编译

#ifndef USE_DEBUG
#define USE_DEBUG 0
#endif

08.do…while

使用宏定义可以将一些较为短小的功能封装,方便使用。宏的形式和函数类似,但是可以节省函数跳转的开销。如何将一个语句封装成一个宏,在程序中常常使用do…while(0)的形式。

#define HELLO(str) do { \
printf("hello: %s\n", str); \
}while(0)

程序示例:

int cond = 1;
if (cond)
    HELLO("true");
else
    HELLO("false");

09.代码剖析

对于比较大的程序,可以借助一些工具来首先把需要优化的点清理出来。接下来我们来看看在程序执行过程中获取数据并进行分析的工具:代码剖析程序。

测试程序:

#include 


#define T 100000

void call_one()
{
    int count = T * 1000;
    while(count--);
}

void call_two()
{
    int count = T * 50;
    while(count--);
}

void call_three()
{
    int count = T * 20;
    while(count--);
}


int main(void)
{
    int time = 10;

    while(time--)
    {
        call_one();
        call_two();
        call_three();
    }
    
    return 0;
}

编译的时候加入-pg选项:

deng@itcast:~/tmp$ gcc -pg  test.c -o test

执行完成后,在当前文件中生成了一个gmon.out文件。

deng@itcast:~/tmp$ ./test  
deng@itcast:~/tmp$ ls
gmon.out  test  test.c
deng@itcast:~/tmp$ 

使用gprof剖析主程序:

deng@itcast:~/tmp$ gprof test
Flat profile:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls  ms/call  ms/call  name    
 95.64      1.61     1.61       10   160.68   160.68  call_one
  3.63      1.67     0.06       10     6.10     6.10  call_two
  2.42      1.71     0.04       10     4.07     4.07  call_three

其中主要的信息有两个,一个是每个函数执行的时间占程序总时间的百分比,另外一个就是函数被调用的次数。通过这些信息,可以优化核心程序的实现方式来提高效率。

当然这个剖析程序由于它自身特性有一些限制,比较适用于运行时间比较长的程序,因为统计的时间是基于间隔计数这种机制,所以还需要考虑函数执行的相对时间,如果程序执行时间过短,那得到的信息是没有任何参考意义的。

将上诉程序时间缩短:

#include 


#define T 100

void call_one()
{
    int count = T * 1000;
    while(count--);
}

void call_two()
{
    int count = T * 50;
    while(count--);
}

void call_three()
{
    int count = T * 20;
    while(count--);
}


int main(void)
{
    int time = 10;

    while(time--)
    {
        call_one();
        call_two();
        call_three();
    }
    
    return 0;
}

剖析结果如下:

deng@itcast:~/tmp$ gcc -pg test.c -o test
deng@itcast:~/tmp$ ./test  
deng@itcast:~/tmp$ gprof test
Flat profile:

Each sample counts as 0.01 seconds.
 no time accumulated

  %   cumulative   self              self     total           
 time   seconds   seconds    calls  Ts/call  Ts/call  name    
  0.00      0.00     0.00       10     0.00     0.00  call_one
  0.00      0.00     0.00       10     0.00     0.00  call_three
  0.00      0.00     0.00       10     0.00     0.00  call_two

因此该剖析程序对于越复杂、执行时间越长的函数也适用。

那么是不是每个函数执行的绝对时间越长,剖析显示的时间就真的越长呢?可以再看如下的例子

#include 


#define T 100

void call_one()
{
    int count = T * 1000;
    while(count--);
}

void call_two()
{
    int count = T * 100000;
    while(count--);
}

void call_three()
{
    int count = T * 20;
    while(count--);
}


int main(void)
{
    int time = 10;

    while(time--)
    {
        call_one();
        call_two();
        call_three();
    }
    
    return 0;
}

剖析结果如下:

deng@itcast:~/tmp$ gcc -pg test.c -o test
deng@itcast:~/tmp$ ./test  
deng@itcast:~/tmp$ gprof test
Flat profile:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls  ms/call  ms/call  name    
101.69      0.15     0.15       10    15.25    15.25  call_two
  0.00      0.15     0.00       10     0.00     0.00  call_one
  0.00      0.15     0.00       10     0.00     0.00  call_three

总结:

在使用gprof工具的时候,对于一个函数进行gprof方式的剖析,实质上的时间是指除去库函数调用和系统调用之外,纯碎应用部分开发的实际代码运行的时间,也就是说time一项描述的时间值不包括库函数printf、系统调用system等运行的时间。

这些实用库函数的程序虽然运行的时候将比最初的程序实用更多的时间,但是对于剖析函数来说并没有影响。

end



一口Linux 


关注,回复【1024】海量Linux资料赠送


精彩文章合集

文章推荐

【专辑】ARM
【专辑】粉丝问答
【专辑】所有原创
专辑linux入门
专辑计算机网络
专辑Linux驱动
【干货】嵌入式驱动工程师学习路线
【干货】Linux嵌入式所有知识点-思维导图


一口Linux 写点代码,写点人生!
评论 (0)
  •   电磁干扰抑制系统平台深度解析   一、系统概述   北京华盛恒辉电磁干扰抑制系统在电子技术快速发展、电磁环境愈发复杂的背景下,电磁干扰(EMI)严重影响电子设备性能、稳定性与安全性。电磁干扰抑制系统平台作为综合性解决方案,通过整合多元技术手段,实现对电磁干扰的高效抑制,确保电子设备稳定运行。   应用案例   目前,已有多个电磁干扰抑制系统在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润电磁干扰抑制系统。这些成功案例为电磁干扰抑制系统的推广和应用提供了有力支持。   二
    华盛恒辉l58ll334744 2025-04-22 15:27 46浏览
  •   卫星通信效能评估系统平台全面解析   北京华盛恒辉卫星通信效能评估系统平台是衡量卫星通信系统性能、优化资源配置、保障通信服务质量的关键技术工具。随着卫星通信技术的快速发展,特别是低轨卫星星座、高通量卫星和软件定义卫星的广泛应用,效能评估系统平台的重要性日益凸显。以下从技术架构、评估指标、关键技术、应用场景及发展趋势五个维度进行全面解析。   应用案例   目前,已有多个卫星通信效能评估系统在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润卫星通信效能评估系统。这些成功案例为卫
    华盛恒辉l58ll334744 2025-04-22 16:34 37浏览
  • 引言:老龄化社会的健康守护需求随着全球老龄化进程加速,老年人的健康管理与生活质量成为社会焦点。记忆衰退、用药混乱、日程遗漏等问题频发,催生了智能健康设备的市场需求。WTR096录音语音芯片,凭借其高度集成的录放音、计时时钟与计划管理功能,为老年人量身打造了一站式健康管理方案,重新定义智能语音时钟的价值。功能亮点:1. 用药安全守护:多维度提醒,拒绝遗忘多时段精准提醒:支持一天内设置多个用药时间(如早、中、晚),适配复杂用药需求。个性化语音定制:家属可录制专属提醒语音(如“上午9点,请服用降压药”
    广州唯创电子 2025-04-22 08:41 90浏览
  • 近期,金融界消息称,江西万年芯微电子有限公司申请一项名为“基于预真空腔体注塑的芯片塑封方法及芯片”的专利。此项创新工艺的申请,标志着万年芯在高端芯片封装领域取得重要突破,为半导体产业链提升注入了新动能。专利摘要显示,本发明公开了一种基于预真空腔体注塑的芯片塑封方法,方法包括将待塑封的大尺寸芯片平铺于下模盒腔体内的基板并将大尺寸芯片的背向表面直接放置于基板上以进行基板吸附;将上模盒盖合于下模盒形成塑封腔,根据基板将塑封腔分为上型腔以及下型腔;将下型腔内壁与大尺寸芯片间的空隙进行树脂填充;通过设置于
    万年芯 2025-04-22 13:28 61浏览
  •   电磁兼容(EMC)故障诊断系统软件解析   北京华盛恒辉电磁兼容故障诊断系统软件是攻克电子设备电磁干扰难题的专业利器。在电子设备复杂度攀升、电磁兼容问题频发的背景下,该软件于研发、测试、生产全流程中占据关键地位。以下为其详细介绍:   应用案例   目前,已有多个电磁兼容故障诊断系统在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润电磁兼容故障诊断系统。这些成功案例为电磁兼容故障诊断系统的推广和应用提供了有力支持。   一、软件核心功能   干扰与敏感分析:深度剖析电磁干
    华盛恒辉l58ll334744 2025-04-22 14:53 53浏览
  • 据国际精益六西格玛研究所(ILSSI)成员大卫·哈钦斯(David Hutchins)的回忆,在“六西格玛”名称出现前,摩托罗拉组建了约100个质量改进团队,接受朱兰博士制作的16盘录像带培训,名为《朱兰论质量改进》(Juran on Quality Improvement),为了推广这种严谨的分析方法(朱兰博士视频中的核心内容),摩托罗拉前首席执行官鲍勃·加尔文创造了“六西格玛”这一标签,用以表彰这种“最顶尖"的方法。大卫·哈钦斯(David Hutchins)是朱兰博士的好友,也为他的工作做
    优思学院 2025-04-22 12:03 63浏览
  •   北京华盛恒辉机场保障能力评估系统软件深度解析   在航空运输业快速发展的背景下,机场保障任务愈发复杂,传统人工评估方式已无法满足高效精准的管理需求。机场保障能力评估系统软件作为提升机场运行效率、保障飞行安全的关键工具,其重要性日益凸显。   应用案例   目前,已有多个机场保障能力评估系统在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润机场保障能力评估系统。这些成功案例为机场保障能力评估系统的推广和应用提供了有力支持。   一、系统功能模块   数据采集与整合模块  
    华盛恒辉l58ll334744 2025-04-22 10:28 81浏览
  •   北京华盛恒辉基于GIS的电磁态势可视化系统软件是将地理空间信息与电磁态势数据相结合,通过图形化手段直观展示电磁环境态势的系统。这类软件在军事、通信、无线电管理等领域具有广泛应用,能够辅助用户进行电磁频谱分析、干扰监测、态势研判和决策支持。以下是关于此类系统的详细介绍:   应用案例   目前,已有多个电磁态势可视化系统在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润电磁态势可视化系统。这些成功案例为电磁态势可视化系统的推广和应用提供了有力支持。   一、系统功能   电磁
    华盛恒辉l58ll334744 2025-04-22 11:44 62浏览
  •   电磁兼容故障诊断系统平台深度解析   北京华盛恒辉电磁兼容(EMC)故障诊断系统平台是解决电子设备在复杂电磁环境下性能异常的核心工具。随着电子设备集成度提升与电磁环境复杂化,EMC 问题直接影响设备可靠性与安全性。以下从平台架构、核心功能、技术实现、应用场景及发展趋势展开全面剖析。   应用案例   目前,已有多个电磁兼容故障诊断系统在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润电磁兼容故障诊断系统。这些成功案例为电磁兼容故障诊断系统的推广和应用提供了有力支持。  
    华盛恒辉l58ll334744 2025-04-22 14:29 53浏览
  • 引言:工业安全与智能化需求的双重驱动在工业安全、环境保护及家庭安防领域,气体泄漏引发的安全事故始终是重大隐患。随着传感器技术、物联网及语音交互的快速发展,气体检测报警器正朝着智能化、低成本、高可靠的方向演进。WT588F02B-8S语音芯片,以“离在线语音更换+多协议通信”为核心优势,为气体检测报警器提供了一套高效、灵活的低成本语音解决方案,助力开发者快速响应市场需求。产品功能与市场需求1. 核心功能:从监测到预警的全流程覆盖实时气体监测:支持一氧化碳、臭氧、硫化氢等多种气体浓度检测,精度可达p
    广州唯创电子 2025-04-22 09:14 65浏览
  • 4 月 19 日,“增长无界・智领未来” 第十六届牛商大会暨电子商务十大牛商成果报告会在深圳凤凰大厦盛大举行。河南业之峰科技股份有限公司总经理段利强——誉峰变频器强哥凭借在变频器领域的卓越成就,荣膺第十六届电子商务十大牛商,携誉峰变频器品牌惊艳亮相,以十几年如一日的深耕与创新,书写着行业传奇。图 1:誉峰变频器强哥在牛商大会领奖现场,荣耀时刻定格牛商大会现场,誉峰变频器强哥接受了多家媒体的专访。面对镜头,他从容分享了自己在变频器行业二十年的奋斗历程与心路感悟。谈及全域营销战略的成功,誉峰变频器强
    电子与消费 2025-04-22 13:22 77浏览
我要评论
0
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦