嵌入式C语言程序调试和宏使用的技巧

点击蓝字

83c028f7443a09e932b07a834269c64d.png

关注我们

01.调试相关的宏

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

具体宏如下:

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

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

测试示例:

#include <stdio.h>int main(void)
{printf("file: %s\n", __FILE__);printf("function: %s\n", __FUNCTION__);printf("line: %d\n", __LINE__);return 0;
}

02.# 字符串化操作符

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

程序示例:

#include <stdio.h>#define DPRINT(expr) printf("<main>%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  
<main>x / y = 0
<main>x + y = 8
<main>x * y = 15

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

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

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

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

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

总结:

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

03.## 连接操作符

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

程序示例:

#include <stdio.h>#define test(x) test##xvoid 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 <stdio.h>#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 <stdio.h>#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");
elseHELLO("false");

09.代码剖析

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

测试程序:

#include <stdio.h>#define T 100000void 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_one3.63      1.67     0.06       10     6.10     6.10  call_two2.42      1.71     0.04       10     4.07     4.07  call_three

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

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

将上诉程序时间缩短:

#include <stdio.h>#define T 100void 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_one0.00      0.00     0.00       10     0.00     0.00  call_three0.00      0.00     0.00       10     0.00     0.00  call_two

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

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

#include <stdio.h>#define T 100void 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_two0.00      0.15     0.00       10     0.00     0.00  call_one0.00      0.15     0.00       10     0.00     0.00  call_three

总结:

在使用gprof工具的时候,对于一个函数进行gprof方式的剖析,实质上的时间是指除去库函数调用和系统调用之外,纯碎应用部分开发的实际代码运行的时间,也就是说time一项描述的时间值不包括库函数printf、系统调用system等运行的时间。这些实用库函数的程序虽然运行的时候将比最初的程序实用更多的时间,但是对于剖析函数来说并没有影响。

*声明:本文于网络整理,版权归原作者所有,如来源信息有误或侵犯权益,请联系我们删除或授权事宜。

c375c2cc40b9b9058e06cd569f15f24b.png

496d04750823d4be33115bc55055aed3.gif

戳“阅读原文”我们一起进步

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/337711.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

matlab中欠定方程组超定方程组_《数值天气预报》:球坐标系中的基本方程组

人们是如何预报天气的&#xff1f;目前的预报方法主要有两种&#xff1a;一种是基于由各种探测资料绘制的天气图&#xff0c;结合历史资料进行分析预测&#xff1b;另一种是基于大气方程组&#xff0c;利用数值解法对其进行求解&#xff0c;从而得到未来时刻的大气状态。后者就…

浏览器总是跳转到缓存界面_跳转到企业缓存之前要考虑的事项

浏览器总是跳转到缓存界面介绍 关系数据库事务是ACID &#xff0c;强大的一致性模型简化了应用程序开发。 由于启用Hibernate缓存是一项配置 &#xff0c;因此&#xff0c;只要数据访问层开始出现性能问题&#xff0c;就转向缓存非常吸引人。 添加缓存层确实可以提高应用程序性…

CryptoTab 服务器_如何架设FTP服务器,如何架设FTP服务器,具体架设方法

FTP服务器&#xff0c;则是在互联网上提供存储空间的计算机&#xff0c;它们依照FTP协议提供服务。 FTP的全称是File Transfer Protocol(文件传输协议)。顾名思义&#xff0c;就是专门用来传输文件的协议。简单地说&#xff0c;支持FTP协议的服务器就是FTP服务器。那么&#xf…

C语言和C++的区别和联系

点击蓝字关注我们C语言和C到底是什么关系&#xff1f;首先C和C语言本来就是两种不同的编程语言&#xff0c;但C确实是对C语言的扩充和延伸&#xff0c;并且对C语言提供后向兼容的能力。对于有些人说的C完全就包含了C语言的说法也并没有错。C一开始被本贾尼斯特劳斯特卢普&#…

hibernate语句_如何优化Hibernate EllementCollection语句

hibernate语句介绍 Hibernate支持三种数据映射类型 &#xff1a; 基本 &#xff08;例如String&#xff0c;int&#xff09;&#xff0c; Embeddable和Entity 。 通常&#xff0c;数据库行被映射到Entity &#xff0c;每个数据库列都与一个基本属性相关联。 当将多个字段映射组…

C++ 虚函数表剖析

点击蓝字关注我们一、概述为了实现C的多态&#xff0c;C使用了一种动态绑定的技术。这个技术的核心是虚函数表&#xff08;下文简称虚表&#xff09;。本文介绍虚函数表是如何实现动态绑定的。二、类的虚表每个包含了虚函数的类都包含一个虚表。我们知道&#xff0c;当一个类&a…

aix pax_通过Pax考试对JBoss Fuse 6.x进行集成测试,第一部分

aix paxJBoss Fuse是一个功能强大的分布式集成平台&#xff0c;具有内置功能&#xff0c;可用于针对集成的微服务部署进行集中式配置管理&#xff0c;服务发现&#xff0c;版本控制&#xff0c;API网关&#xff0c;负载平衡&#xff0c;故障转移等。 JBoss Fuse 6.x构建在Fabri…

android王者调不了界面,王者荣耀登录界面怎么改?登录界面更改教程[多图]

王者荣耀登录界面怎么改&#xff1f;许多玩家都想更改自己登陆的界面&#xff0c;但是都不清楚&#xff0c;下面就让安卓乐园小编为大家带来&#xff0c;登录界面更改教程。王者荣耀登录界面怎么改&#xff1f;1、安卓手机打开文件管理&#xff0c;找到根目录下Android/data/co…

y空间兑换代码_Python爬虫实战:QQ空间全自动点赞工具

QQ空间自动点赞前景提要目标确定分析介绍登陆获取cookie寻找XML寻找可变参数获取第一个空间动态寻找点赞所需的URL寻找可变参数功能提升到秒赞全部代码最后还是希望你们能给我点一波小小的关注。奉上自己诚挚的爱心私信小编01即可获取大量Python学习资料前景提要因为我周围的小…

学点 STL C++ 无序容器和元组

点击蓝字关注我们无序容器我们已经熟知了传统 C 中的有序容器 std::map/std::set&#xff0c;这些元素内部通过红黑树进行实现&#xff0c; 插入和搜索的平均复杂度均为 O(log(size))。在插入元素时候&#xff0c;会根据 < 操作符比较元素大小并判断元素是否相同&#xff0c…

html中的文档格式及举例,跟我一起从零开始学习WebAssembly(三)、最简单的例子hello world(使用自定义HTML模板)...

文章目录创建C代码片创建我们的自定义HTML模板文件编译运行实例有时我们想要使用我们自定义HTML模板。让我们来看看我们如何做到这一点。创建C代码片首先&#xff0c;创建一个名为hello2的目录。其次&#xff0c;在该目录下创建一个名为hello2.c文件。并将以下C代码保存在文件中…

设计模式适配器模式_21世纪的设计模式:适配器模式

设计模式适配器模式这是我的演讲的第三部分&#xff0c;“ 21世纪的设计模式” 。 适配器模式桥接世界。 在一个世界中&#xff0c;我们有一个概念的界面。 在另一个世界&#xff0c;我们有不同的界面。 这两个接口有不同的用途&#xff0c;但有时我们需要进行转移。 在编写良…

excel单元格斜线_掌握这20个Excel技巧,小白轻松变大神

掌握一些Excel小技巧&#xff0c;可以让你的工作效率翻倍&#xff0c;原本半个小时才能搞定的&#xff0c;现在几秒就可以轻松搞定。1、调整单元格大小选中表格&#xff0c;将光标移到表格顶部边框处&#xff0c;等其变成双向箭头即可移动。2、快速插入空行选中行&#xff0c;按…

html鼠标滚动效果代码,JS+CSS实现大气清新的滑动菜单效果代码

本文实例讲述了JSCSS实现大气清新的滑动菜单效果代码。分享给大家供大家参考&#xff0c;具体如下&#xff1a;这是一款比较大气清新的滑动导航菜单&#xff0c;CSS和JavaScript配合完成&#xff0c;鼠标放到一级菜单上&#xff0c;会滑出二级的菜单&#xff0c;兼容性也不错&a…

docker和java容器_使用Docker容器和Java EE进行持续交付

docker和java容器组织需要一种使应用程序交付快速&#xff0c;可预测和安全的方法&#xff0c;而诸如docker之类的容器所提供的敏捷性则可以帮助开发人员实现这一目标。 对于Java EE应用程序&#xff0c;这可以在容器中打包应用程序&#xff0c;应用程序服务器和其他依赖项&…

alientek ministm32液晶显示程序_佳显12864中文字库液晶专业生产液晶显示模块

GDRAM&#xff1a;(Graphic Display RAM)&#xff1a;图形显示RAM&#xff0c;这一块区域用于绘图&#xff0c;往里面写啥&#xff0c;屏幕就会显示啥&#xff0c;它与DDRAM的区别在于&#xff0c;往DDRAM中写的数据是字符的编码&#xff0c;字符的显示先是在CGROM中找到字模&a…

C++ 面试考点(一)

点击蓝字关注我们C 基础1、引用和指针的区别&#xff1f;初始化:引用在定义的时候必须进行初始化&#xff0c;并且不能够改变指针在定义的时候不一定要初始化&#xff0c;并且指向的空间可变访问逻辑不同:通过指针访问对象, 用户需要使用间接访问通过引用访问对象, 用户只需使用…

dojo还有人用吗_我的Dojo中有一个Mojo(如何编写Maven插件)

dojo还有人用吗我一直忙于在工作中使用Maven的腋窝。 对于很多开发人员&#xff0c;我会听到&#xff1a;“那又怎样。” 区别在于&#xff0c;我通常在无法直接访问Internet的环境中工作。 因此&#xff0c;当我说我经常使用Maven时&#xff0c;这意味着某些事情。 依赖地狱 …

html5 css3炫酷效果,28种纯CSS3炫酷loading加载动画特效

这是一组效果非常炫酷的纯CSS3 Loading加载动画特效。这组loading动画共有27种不同的效果。每一种loading动画都是通过CSS3的keyframes帧动画来完成的&#xff0c;每一个加载动画都构思新颖&#xff0c;效果非常的酷。安装可以通过bower来按钮这个loading动画特效&#xff1a;b…

中点和中值滤波的区别_频谱仪和EMI测试接收机什么区别?安泰维修中心分享

测试人员在选择使用射频仪器的时候都在纠结选择频谱仪还是测试接收机又或者信号分析仪。下面由安泰频谱分析仪维修中心分享频谱仪和EMI测试接收机什么区别&#xff1f;测量接收机是什么&#xff1f;频谱仪和信号分析仪什么区别&#xff1f;信号源分析仪是什么&#xff1f;一、频…