第3章 Linux下应用程序开发基础 -67-
● 单步执行程序; ● 修改变量的值。
在使用GDB调试程序之前,必须在用GCC编译源文件时加上-g选项。可在Makefile文件中定义CFLAGS变量:
CFLAGS= -g
gdb程序调试的对象是可执行文件,而不是程序的源代码文件。然而,并不是所有的可执行文件都可以用gdb调试。如果要让产生的可执行文件可以用来调试,需在执行gcc命令编译程序时,加上-g参数,指定程序在编译时包含调试信息。调试信息包含程序里的每个变量的类型和在可执行文件里的地址映射以及源代码的行号。gdb 利用这些信息使源代码和机器码相关联。
在命令行上输入gdb并按回车键就可以运行gdb了,如果一切正常的话,将启动gdb,可以在屏幕上看到以下的内容:
GNU gdb Red Hat Linux (5.3post-0.20021129.18rh) Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type \
There is absolutely no warranty for GDB. Type \This GDB was configured as \(gdb)
启动gdb后,可以在命令行上指定很多的选项。输入:help
可以获得gdb的帮助信息。如果想要了解某个具体命令(比如break)的帮助信息,在gdb提示符下输入下面的命令:break
屏幕上会显示关于break的帮助信息。从返回的信息可知,break是用于设置断点的命令。
1. GDB基本命令
gdb支持很多的命令且能实现不同的功能。这些命令从简单的文件装入到允许你检查所调用的堆栈内容的复杂命令, 下面列出了在使用gdb 调试时会用到的一些命令。
file FILE: 装载指定的可执行文件进行调试; break NUM :在指定的行上设置断点,NUM为行号;
Tbreak命令:设置临时断点。它的语法与break相同。区别在于用tbreak设置的断点执行一次之后立即消失;
watch命令:设置监视点,监视表达式的变化;
awatch命令:设置读写监视点。当要监视的表达式被读或写时将应用程序挂起。它的语法与watch命令相同;
-68- 嵌入式Linux系统开发与应用实验教程
bt:显示所有的调用栈帧,该命令可用来显示函数的调用顺序;
Clear: 删除设置在特定源文件、特定行上的断点,其用法为:clear FILENAME:NUM; Continue: 继续执行正在调试的程序,该命令用在程序由于处理信号或断点而导致停止运行时;
display EXPR :每次程序停止后显示表达式的值,表达式由程序定义的变量组成; help NAME: 显示指定命令的帮助信息;
info break: 显示当前断点清单,包括到达断点处的次数等; info files: 显示被调试文件的详细信息; info func: 显示所有的函数名称;
info local: 显示当函数中的局部变量信息; info prog: 显示被调试程序的执行状态; info var: 显示所有的全局和静态变量名称; kill: 终止正被调试的程序; list:显示源代码段;
make:在不退出 GDB 的情况下运行 make 工具;
next: 在不单步执行进入其他函数的情况下,向前执行一行源代码; step:执行一行源代码而且进入函数内部; print EXPR:显示表达式 EXPR 的值; run:执行当前被调试的程序; Quit命令:退出gdb。
2.GDB应用举例
编写一个bugging.c程序,程序清单如下:
#include
static char buff [256]; static char* string; int main () {
printf (―Please input a string:‖ ); gets (string);
printf (―Your string is: %s‖, string);
}
第3章 Linux下应用程序开发基础 -69-
上面这个程序非常简单,其目的是接受用户的输入,然后将用户的输入打印出来。该程序使用了一个未经过初始化的字符串地址 string,因此,编译并运行之后,将出现 Segment Fault 错误。为了查找该程序中出现的问题,我们利用 gdb,并按如下的步骤进行:
① 首先用gcc进行编译:gcc –o bugging –g bugging.c ;(注意,中间的文件名不
能加后缀。)
② 调用GDB调试程序:gdb bugging ;
③ 用file命令载入可调试程序:(gdb) file bugging ; (gdb)是提示符,在这提示符下可
以输入命令,直到退出。
④ 使用 where 命令查看程序出错的地方:(gdb) where
⑤ 利用 list 命令查看调用 gets 函数附近的代码:(gdb) list ; list每次只能显示10
行程序代码,如往下查看,可再次输入list往下查看。
⑥ 用 print 命令查看 string 的值:print ; (唯一能够导致 gets 函数出错的因素就
是变量 string,在gdb中,我们可以直接修改变量的值,只要将string取一个合法的指针值就可以了。)
⑦ 在第11 行处设置断点:(gdb) break 11 ; ⑧ 用run命令运行程序:(gdb) run 。
程序重新运行到第 11 行处停止,这时,我们可以用 set variable 命令修改 string 的取值;然后继续运行,将看到正确的程序运行结果。
3.4 嵌入式软件开发的编程风格
在进行软件开发的过程中,遵从严格的编程规范,培养良好的编程习惯是提升程序代码的可读性和可维护性的重要保证。我们对于应用软件的编程风格所推崇的基本原则是:风格统一、便于理解、容易维护。
3.4.1 C语言程序命名规则
C语言是目前使用最广泛的嵌入式系统编程语言。C语言是一种简洁的语言,那么,其程序的命名也应该是简洁的。在实际应用过程中,命名规则是非常难以统一的。但基于简洁、可读和易维护的基本原则,我们把握以下几个原则即可。
● 使用英文命名
有些人在编程时,喜欢用拼音给函数或变量命名,这样做是不妥的,因为并非所有人的发音都是标准的。特别是现在,软件开发的日益国际化和软件外包(out sourcing)的飞速发展,简单扼要的英文命名是必须的。在用英文命名时,用词力求准确和简洁,所有英文词或缩略词的第一个字母必须大写。
● 匈牙利(Hungarian)命名法
-70- 嵌入式Linux系统开发与应用实验教程
Microsoft公司或其他一些跨国公司都倡导使用该方法。虽然这种方法比较繁琐,但习惯成自然。要掌握匈牙利命名法的精髓,即它的一致性和望文生义。
● 简洁性
文件名的长度最好不要超过8个字符,前4个或前N个字符为模块缩写命名,后4个或后N个字符为文件本身命名。
● 局部性和全局性的区分
任何宏定义、常数定义、类型定义、变量说明和函数定义等,都有所谓的局部性和全局性的特点。局部性指的是该定义仅仅会被很小部分的函数引用,有函数级模块或C源程序级引用之分。全局性指的是该定义困难会被所有的函数引用。我们建议使用前缀法(PreFix)来区分它们。我们知道任何宏、常数、类型、变量和函数等,都必须在某个特定的C源文件或H头文件中定义或说明,所谓的前缀法就是把该文件名(后缀除外)作为这些宏、常数、类型、变量和函数的前缀名,它们所完成的功能或表现的意义作为附属的后缀描述名。
对于所有模块或C源程序级的局部定义和全局定义的命名,建议利用前缀名和后缀描述名的连接方式——以有无下划线“_”来区分,即全局定义有“_”,而局部定义没有“_”。
对于所有函数级的局部定义的命名,建议用它所完成的功能或表现意义的英文词或缩略词来命名,不需要任何前缀。
3.4.2 程序书写格式
编写程序使之程序代码看上去美观大方、层次分明、前后一致、清晰整洁、模块清晰而且便于阅读是每个编程人员应该遵从的原则。下面就如何改善源代码的书写格式,提升程序的可读性和可维护性,提供一些建议。
● 缩进格式
缩进格式是影响代码视觉效果的重要因素之一,缩进是为了清楚地定义一个快的开始和结束。一些程序员利用键盘上的Tab键来完成这项工作,也有人喜欢用空格键。Tab键和空格键孰优孰劣?应该说是各有利弊:Tab键快捷方便,但在跳格长度不同的编辑器中,所表现的缩进效果是不一样的,甚至会发生排版混乱;用空格来缩进,格式是固定不变的,可以写出定型的代码风格,但是需要大量重复按键,降低了编码速度。我们建议Tab键的跳格长度设置为4个或8个字符的宽度,并且在不同的编辑器中保持同样的设定,以维护代码的风格一致。同时采用缩进格式还会给我们带来另外一个好处,即它能在程序嵌套层数太所的时候给予警告———程序的可读性可能有问题,此时应该修改程序。
● 空格和空行的运用
合理地使用空格和空行,可以使程序看起来更清晰,模块结构更加明显。特别是在函数调用中分隔参数,在赋制值语句和表达式中分隔运算符号等需要使程序表现得更加清晰明了的地方,空格的运用是很好的手段。在编写某个相对独立操作的代码块、功能相似的代码块、形式相似的代码块,以及函数与函数之间,空行的运用是使程序结构更加明显的好方法。
● 注释的书写
第3章 Linux下应用程序开发基础 -71-
注释通常包括变量注释、函数注释和语句注释。一般情况下,注释能够帮助程序阅读者快速地了解该段程序的编写目的,但是过多过滥的注释也是危险的。因为可能发生程序的可读性大大降低的糟糕情况,同时请不要试图去将你的代码注释写得更好,而是应该将程序代码写得更好,不要花费大量的时间和精力去解释这些糟糕的代码。通常情况下,注释只说明代码的功能,而不会说明其实现的原理,因为基于一个好的软件开发流程而产生出来的软件代码,它的编制基础是详细设计和实现文档,软件代码仅仅是该文档的产出物。这也说明程序中的注释仅仅是完成了简单说明和介绍的功能。
在实际应用过程中,应该避免把注释插到函数体内,应写在函数前面,以说明其功能。同样对于必须加以解释的代码或变量,注释也应写在它们的前面。
● 大括号的运用
大括号(“{”和“}”)的处理在C程序书写风格中也是很重要的,与缩进格式不同,几乎没有任何理由可以说服程序员去选择一种风格而不是选择另外一种风格,我们建议采用统一的风格来处理大括号的排版,而不会在意函数体还是其他——开始和结束的括号都放在下一行的第一列。
● 函数的书写格式
函数应该是短小精悍的,它的代码长度应该有限。也就是说,一个函数的最大长度和函数的复杂程度以及缩进大小成反比。如果你计划编写一个简单但长度相对较长的函数,并且已经对不同的情况做了很多细化的工作,那么编写一个稍长的函数也是可以接受的。但是,假如计划编写一个很复杂的函数,而且你已经估计到,其他人很难读懂这个函数,我们建议请重新考虑这个函数,并将它们分割成更小的函数。
在进行函数设计时,还需要考虑的是,该函数困难要定义的局部变量的数量,理论上,这些变量不应该超过10个,否则就有可能出错。
● 其他方面的注意事项
(1) 如果参数太多,不能放在同一行,则在每行参数开头处对齐。 (2) 当一个表达式需要分成多行书写时,应该在操作符之前分割。
(3) 尽量不要让两个不同优先级的操作符出现在相同的对齐方式中,应该增加括号通
过代码缩进表示嵌套关系。
(4) 不要在声明多个变量时跨行,每一行都应以一个新的声明开头。
(5) 当一个if-else 语句中嵌套了另一个if-else语句时,应该用大括号把if-else语句
括起来。
(6) 尽量避免在if条件中进行赋值运算。
(7) 如果没有声明,不要将BOOL值TRUE和FALSE对应与1和0进行编程,大多
数编译器会将FALSE视为0,任何非0值都是TRUE。我们建议重新定义BOOL值并锁定0和1。
(8) 预防和避免非法指针的使用。
3.4.3 可移植性编程