版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第第页嵌入式软件项目中的一些常用套路与技巧01.调试相关的宏
在(Linux)使用gcc编译程序的时候,对于调试的语句还具有一些特殊的语法。
gcc编译的过程中,会生成一些宏,可以使用这些宏分别打印当前源文件的信息,主要内容是当前的文件、当前运行的函数和当前的程序行。
具体宏如下:
__FILE__
当前程序源文件
(char*)__FUNC(TI)ON__
当前运行的函数
(char*)__LINE__
当前的函数行
(int)
这些宏不是(程序代码)定义的,而是有编译器产生的。这些信息都是在编译器处理文件的时候动态产生的。
「测试示例:」
#include
int
main(void){
printf("file:
%s",
__FILE__);
printf("function:
%s",
__FUNCTION__);
printf("line:
%d",
__LINE__);
return
0;}
02.#字符串化操作符
在gcc的编译系统中,可以使用#将当前的内容转换成字符串。
「程序示例:」
#include
#define
DPRINT(expr)
printf("%s
=
%d",
#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
(te)st.c
deng@itcast:~/tmp$
./a.out
x
/
y
=
0x
+
y
=
8x
*
y
=
15
#expr表示根据宏中的参数(即表达式的内容),生成一个字符串。该过程同样是有编译器产生的,编译器在编译源文件的时候,如果遇到了类似的宏,会自动根据程序中表达式的内容,生成一个字符串的宏。
这种方式的优点是可以用统一的方法打印表达式的内容,在程序的调试过程中可以方便直观的看到转换字符串之后的表达式。
具体的表达式的内容是什么,有编译器自动写入程序中,这样使用相同的宏打印所有表达式的字符串。
//打印字符#define
debugc(expr)
printf("
%s
=
%c",
#expr,
expr)//打印浮点数#define
debugf(expr)
printf("
%s
=
%f",
#expr,
expr)//按照16进制打印整数#define
debugx(expr)
printf("
%s
=
0X%x",
#expr,
expr);
由于#expr本质上市一个表示字符串的宏,因此在程序中也可以不适用%s打印它的内容,而是可以将其直接与其它的字符串连接。
因此,上述宏可以等价以下形式:
//打印字符#define
debugc(expr)
printf("
#expr
=
%c",
expr)//打印浮点数#define
debugf(expr)
printf("
#expr
=
%f",
expr)//按照16进制打印整数#define
debugx(expr)
printf("
#expr
=
0X%x",
expr);
「总结:」
#是(C语言)预处理阶段的字符串化操作符,可将宏中的内容转换成字符串。
03.##连接操作符
在gcc的编译系统中,##是C语言中的连接操作符,可以在编译的预处理阶段实现字符串连接的操作。
「程序示例:」
#include
#define
test(x)
test##xvoid
test1(int
a){
printf("test1
a
=
%d",
a);}void
test2(char
*s){
printf("test2
s
=
%s",
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",
a,
b);
DEBUG("a
=
%x
b
=
%x",
a,
b);
DEBUG("s
=
%s",
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",
a,
b);
DEBUG("a
=
%x
b
=
%x",
a,
b);
DEBUG("s
=
%s",
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",
str);
}while(0)
「程序示例:」
int
cond
=
1;if
(cond)
HELLO("true");else
HELLO("false");
09.代码剖析
对于比较大的程序,可以借助一些工具来首先把需要优化的点清理出来。接下来我们来看看在程序执行过程中获取数据并进行分析的工具:代码剖析程序。
「测试程序:」
#include
#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$
lsgmon.out
test
test.cdeng@itcast:~/tmp$
「使用gprof剖析主程序:」
deng@itcast:~/tmp$
gprof
testFlat
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
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
testdeng@itcast:~/tmp$
./test
deng@itcast:~/tmp$
gprof
testFlat
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
100void
call_one(){
int
count
=
T
*
1000;
while(count--);}void
call_two(){
int
count
=
T
*
100000;
while(count--);}void
call_three(){
int
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年教育机构校园宣传栏设施采购及安装合同3篇
- 二零二五年度木材防腐处理木工班组承包合同样本4篇
- 2025年食堂食材安全认证与采购合同3篇
- 2025版家居建材行纪合同范本2篇
- 第八章生命体征的评估与护理护理学基础88课件讲解
- 2025年保洁防疫服务协议
- 2025年加盟连锁店经销合作协议范例
- 2025年大型综合市场用水电合同
- 2025年专利知识产权技术权利使用许可转让合同
- 二零二五版闭门会议知识产权授权与保密条款合同3篇
- 2024年萍乡卫生职业学院单招职业技能测试题库标准卷
- 2024年高考数学(理)试卷(全国甲卷)(空白卷)
- DB32-T 4444-2023 单位消防安全管理规范
- 临床三基考试题库(附答案)
- 合同签订执行风险管控培训
- 九宫数独200题(附答案全)
- 人员密集场所消防安全管理培训
- JCT587-2012 玻璃纤维缠绕增强热固性树脂耐腐蚀立式贮罐
- 典范英语2b课文电子书
- 员工信息登记表(标准版)
- 春节工地停工复工计划安排( 共10篇)
评论
0/150
提交评论