您好,欢迎来到保捱科技网。
搜索
您的当前位置:首页Linux下应用程序开发基础

Linux下应用程序开发基础

来源:保捱科技网
-42- 嵌入式Linux系统开发与应用实验教程

第三章 Linux下应用程序开发基础

在Linux系统下进行应用程序开发,必须先熟悉GNU/Linux系统下的开发工具的使用。在本章中,我们主要介绍全屏编辑器vi(VIM)、集成开发工具KDevelop、交叉编译工具GNU GCC套件的功能及使用。另外,为了提升应用程序的可读性和可维护性,在第4小节专门就嵌入式应用程序的编程风格进行简单描述。

3.1 vi编辑器

3.1.1 vi简介

vi是Linux系统最常用的全屏编辑器,所有的Linux机器都提供该编辑器,而Linux里提供的是vi的加强版——VIM,但同vi是完全兼容的。vi的原意是―visual interface‖,即可视编辑器,用户键入的内容会立即被显示出来,而且其强大的编辑功能可以同任何一种最新的编辑器相媲美。它在Linux上的地位就仿佛Edit程序在DOS上一样。它可以执行输出、删除、查找、替换、块操作等众多文本操作,而且用户可以根据需要对其进行定制,这是其他编辑程序所没有的。vi不是一个排版程序,不象Word或WPS那样可以对字体、格式、段落等其他属性进行编排,它只是一个文本编辑程序。

3.1.2 vi基本操作模式

vi有三种基本操作模式,它们是:

● 插入模式:插入模式即为文本编辑模式。这个模式用于输入文本或程序,输入字母i,即进入插入模式,按ESC键返回到命令模式。

● 可视命令模式:一般当进入vi时,会首先进入可视命令模式,这是vi的启动默认模式,这种模式用于输入操作命令。

● 冒号命令模式:在冒号命令模式下,所有命令都要以“:”开始,所输入的字符均作为命令来处理。

3.1.3 vi的进入和退出

在Linux系统的终端命令行模式下键入命令vi,后面跟上想要编辑(或者建立)的文件名,vi可以自动载入所要编辑的文件或是开启一个新文件。如果想用Linux桌面上的VIM编辑器,可以从Linux桌面上点击主菜单→编程→VIM,进入VIM启动界面,然后可通过选择上方的菜单来进行操作。

vi的退出,可以在冒号命令行模式下使用命令“:wq”或者―:q!‖,前者的功能是写文件并从vi中退出,后者的功能是从vi中退出,但不保存所作的修改(注意冒号)。VIM的退

第3章 Linux下应用程序开发基础 -43-

出也可通过选择编辑器上方的菜单来完成。

3.1.4 vi的命令

(1) 光标的移动

用户除了通过按键盘的上、下、左、右箭头键来移动光标,还可以利用vi提供的字母键h(左移)j(下移)k(上移)l(右移)来完成光标的移动。

(2) 删除和替换类命令 x—删除光标所在位置的字符。 dw—删除光标处的单词。 d$—从当前光标删除到行末。 dd—删除光标所在行。

r—输入r和一个字符替换光标所在位置的字符。 u—恢复前一次所做的操作。

(3) 更改命令

cw—改变一个单字/单词的部分或者全部。更改类指令可以使用同删除类命令所使用的对象参数。

(4) 置入命令

p—将最后一次删除的内容置入光标之后。

(5) 配对括号的查找

%—可以查找配对的括号 )、]、}。在程序调试时,这个功能用来查找不配对的括号是很有用的。

(6) 提取和合并文件

:r 文件名—向当前文件中插入另外文件的内容,将文件提取进来。

(7) 打开类命令

输入小写的 o 可以在光标下方打开新的一行并将光标置于新开的行首,进入插入模式。 输入大写的 O 可以在光标上方打开新的一行并将光标置于新开的行首,进入插入模式。

(8) 查找字符串

在命令行键入字符“/”,后面加上要搜索的字符串,然后按回车键,编辑程序将执行正向

搜索(从光标所在的位置向文件末尾方向),并在找到指定字符串后,将光标停在该字符串的

-44- 嵌入式Linux系统开发与应用实验教程

开头;键入n命令可以继续执行搜索,找出这一字符串下次出现的位置,用字符“?”取代“/”,可以实现反向搜索(从光标所在的位置向文件开头方向)。

(9) 存盘/退出命令

:w —保存到文件,存盘不退出。 :q!—放弃任何改动而退出,即强行退出。 :w!—对于只读文件强行存档。 :wq—存档并退出vi。

:#,# w 文件名—保存文件的部分内容。这里的 #,# 就是顶端行号和底端行号。

(10) 在线帮助命令

VIM拥有一个细致全面的在线帮助系统。要启动该帮助系统,请选择如下三种方法之一: ◆ 按下 键 (如果键盘上有的话) ◆ 按下 键 (如果键盘上有的话) ◆ 输入 :help <回车>

输入 :q <回车> 可以关闭帮助窗口。

如果要在vi执行期间,转到shell执行,可使用(!)将执行系统命令。例如,在vi的命令状态,列出当前目录内容,可以输入:

:!ls

注意:在文本编辑模式下,不能输入命令,必须先按下Esc键,返回命令模式。假如用户不知身处何态,也可以按下Esc键,这时不管处于何态,都会返回命令模式。

3.1.5 VIM新增功能

VIM意为VI Improved,与vi 99%向下兼容,而且VIM提供了许多vi不具备的功能,内置了诸多函数,因此建议大家熟悉了vi的基本操作以后,不妨转向VIM。选择一款好的编辑器是进行高效编程的第一步,VIM以其强大的功能和无穷魅力将提升在Linux下编程的效率。下面介绍VIM新增功能及特点。

● VIM支持语法制导和自动缩进。VIM会根据文件的后缀名,如:*.c、*.cpp、*.cc,等,自动设置语法制导(Syntax Highlighting)。C/C++中的一些保留字,如:if、for、include,等等,就会有不同的颜色,这样就会减少程序员犯错的可能,阅读程序的时候相对来说也轻松很多。VIM也支持根据GNU的风格自动缩进,这样程序员编写出来的代码就更易阅读,而且也有利于培养好的编程风格。

● VIM有一套优秀的语法高亮机制。利用这一机制可帮你找到程序中存在的错误。语法高亮用特殊的颜色来显示注释,你可以快速发现哪些部分应该是一个注释. 但是并没有被语法高亮指出来。对程序员来说,忘记注释的结束标记*/是很正常的事,这在只有黑白两色

第3章 Linux下应用程序开发基础 -45-

的文本中可不是一件省油的事。没有正确匹配的括号也可被语法高亮指出。一个没有被正确匹对的括号\会被一个亮红色的背景特别指出。你可以使用 \"%\"命令看一看它应该跟谁匹配,然后在正确的位置补上一个。

● VIM提供了快速定位的功能。如果你找到一个变量并且想找出这个变量还在其它哪些地方出现,可以使用“*”命令,它查找下一个匹配的目标。 如果你设置了incsearch选项,VIM将会以反白显示出第一个被找出的匹配。如果你设置了hlsearch选项,Vim将会高亮显示所有查找到的匹配,这种策略可以让你对要查找的内容有一个概括的了解,如果你在程序代码中使用这一功能,它能显示出所有引用某个变量的地方。

● VIM提供了一个补全机制。当输入一个比较长的函数名或变量名时,只要输入前几个字符,然后通过按下CTRL-N键,VIM的补全机制就会补全剩下的字符。这个功能不仅节省了你输入程序的时间,还减少了手工输入代码时出错的机会。

● 使用tags文件快速跳转到函数定义处。在写C程序时,经常要花时间找到一个函数的定义,在上面提到用“*”命令可以查找这个函数名都在哪些地方出现过,但在你到达真正的目标之前,可能还有符合你的查找条件的很多个匹配(如注释中出现的该函数)干扰你。VIM提供了一个Ctags程序,可以帮助编程人员很容易的浏览源代码。用下面的命令可以在源代码的根目录下创建“tags”文件:

[/home/brimmer/scr]# ctags -R

“-R表示递归创建,也就包括源代码根目录下的所有子目录下的源程序。“tags”文件中包括用#define定义的宏、枚举型变量的值、函数的定义、名字空间、类型定义、变量、类等对象的列表。VIM用这个“tags”文件来定位那些做了标记的对象。定位这些对象的方法如下:

① 用命令行。在运行VIM的时候加上“-t”参数,例如:

vim -t foo_bar

这个命令将打开定义“foo_bar”(变量或函数或其它)的文件,并把光标定位到这行。 ② 在VIM编辑器内用“:ta”命令,例如:

:ta foo_bar

③ 最方便的方法是把光标移到变量名或函数名上,然后按下“Ctrl-]”,用“Ctrl-o”返

回原来的地方。

注意:运行VIM的时候,必须在“tags”文件所在的目录下运行。否则,运行VIM的时候还要用“:set tags=”命令指出“tags”文件的路径,这样VIM才能找到“tags“文件。

● VIM提供了它自己的“:make”命令。在VIM编辑器的环境下用“:make ”命令就可以编译你的程序项目,捕获编译的错误/警告并允许你直接跳转到引起这一错误/警告的程序行上去。(前提是在当前目录下有Makefile文件。)运行完“:make”之后,如果程序中有错误,就会显示出来,这时候,光标会自动指向第一个出现错误的地方,而且你还可以看到错误提示。然后,你就可以改正错误。

-46- 嵌入式Linux系统开发与应用实验教程

3.2 集成开发工具KDevelop

3.2.1 Kdevelop的简介

Kdevelop是一套功能强大的集成开发环境,其整合了开发程序所需的编译器、连接器、除错工具、版本控制工具等,可以用Kdevelop快速地建立各式各样的应用程序。Kdevelop基于X-Window系统,界面友好,程序员在一个环境中就可以完成分析、设计、编译、调试、运行等整个流程,尽管KDevelop源于KDE/Qt,但并不局限于KDE/Qt,它同时还支持GNOME/gtk+、传统字符界面C/C++开发,KDevelop为快速地开发C/C++应用程序提供强有力的开发工具。

3.2.2 启动Kdevelop

如果是第一次使用Kdevelop,Kdevelop会先启动“Kdevelop设置”,进行Kdevelop的环境设定,共需要完成9个步骤的设置工作,如图3-1所示。

图3-1 Kdevelop设置 图3-2 选择语法高亮风格

单击―Kdevelop设置欢迎画面对话框中的【下一步】按钮开始进行Kdevelop的设定。Kdevelop设置的第二项设定为“选择语法高亮风格”,这里选择缺省的“Kdevelop 2.0 风格”,如图3-2所示。

选择喜欢的语法高亮表示风格后,单击【下一步】按钮进入“用户交换界面模式”的选择窗口,如图3-3所示。

同样选择好用户交换界面模式后,单击【下一步】按钮进入“Kdevelop中所使用的工具检测窗口”,如图3-4所示。

第3章 Linux下应用程序开发基础 -47-

图3-3 选择用户交换界面 图3-4 工具检测

Kdevelop所使用的工具检测窗口单击“工具程序检测窗口”对话框中的【下一步】按钮,进行下一个步骤。下一个步骤为―寻找Qt文档‖,并设定文件路径,一般而言这个步骤应该会成功完成。Qt文档查找界面如图3-5所示。

图3-5 Qt文档查找 图3-6 安装过程成功完成窗口

单击“寻找Qt文档”对话框之中的【下一步】按钮,进入下一个步骤。下一个步骤为“寻找KDE程序库文件”,同样,一般而言这个步骤也应该会成功完成。再缺省完成两步以Kdevelop设置将显示“安装过程成功完成”的对话框,如图3-6所示,此为Kdevelop设置的最后步骤,单击【下一步】按钮,稍待一会Kdevelop便会启动。

3.2.3 创建一个新项目

-48- 嵌入式Linux系统开发与应用实验教程

在Kdevelop中开发C程序,需要用创建项目的方式进行。请执行“项目/新建”命令,打开应用程序向导对话框,创建过程如图3-7所示。

图3-7 创建新项目

选择要创建程序的种类,这里选择C程序,然后单击【后一页】按钮进行下一步骤。下一个步骤为关于项目信息的设定,如图3-8所示,在此输入项目的名称、目录、版本号码、作者姓名、以及作者电子邮件地址,后两项可以不填。可把“生成源文件和头文件”前的勾去掉,(如不去掉勾,系统会自动生成一些源代码),单击【创建】按钮,等屏幕出现“Ready”信息后,单击“退出”,项目便生成了。

图3-8 输入项目信息

第3章 Linux下应用程序开发基础 -49-

3.2.4 输入源程序

创建项目后,下边要输入源程序了,选择文件菜单中的“新建”命令,新建一个“C/C++程序”,如图3-9所示,或者在Kdevelop窗口的左边选择源程序,这时系统自动按照其选定的文件头摸板生成一个空文件,这时可开始输入程序,例如,输入如图3-10所示程序:

图3-9 新建C/C++文件

图3-10 输入源程序

-50- 嵌入式Linux系统开发与应用实验教程

3.2.5 项目的编译与执行

写好程序后,先在“建立”菜单中,点击“autoconf和automake(A)”,然后,执行“建立/编译”命令进行程序的编译,如果程序没有出错,经过一段时间的编译,将看到Kdevelop下方的信息框之中显示编译成功的信息,如上图所示。接着便可以执行应用程序了。选择“建立/执行”命令进行程序的连接,或者点击工具栏中的“执行”按钮,若没有发生问题,就会在另一个窗口中看到程序的执行结果,如上例终端中就会输出“ Hello world!”这句话,如图3-11所示。

图3-11 程序运行成功界面

3.3 交叉编译工具GNU GCC

3.3.1 GNU GCC简介

GUN 编译器集合(GCC)是一个“免费”的C/C++工具链,它有力地支持了Linux系统的发展,由于它被看作许多嵌入式处理器的一个交叉编译器,所以在嵌入式应用程序开发中是一个非常流行的交叉编译工具,它包括C/C++交叉编译器arm-elf-gcc、汇编器arm-elf-as、连接器arm-elf-ld、库管理器arm-elf-ar、工程管理器make、调试工具gdb和其他一些实用程序。它的基本功能包括如下几点:

1. 输出预处理后的C/C++源程序; 2. 输出C/C++源程序的汇编代码; 3. 输出二进制目标文件; 4. 生成静态库; 5. 生成可执行程序; 6. 转换文件格式。

第3章 Linux下应用程序开发基础 -51-

在这里我们还要理清Linux软件开发中的一些基本概念。

● 编辑器与编译器的概念:编辑器是输入程序代码的裁体。Linux提供了几款编辑器,如vi(VIM),还有KDevelop等。而编译器则是将源程序转换成机器可识别的二进制代码的工具。

● 交叉编译器的基本概念:交叉编译器本身是运行在host(宿主机)上,而交叉编译器所生成的代码则运行在target(目标机)上。

● Linux操作系统之上编程与操作系统之下编程的区别:在Linux操作系统之上编程,例如,有一个C语言写的源文件hello.c,要对它进行编译,可用gcc编译器,命令行如下:

# gcc –o hello.o hello.c

其中gcc是Linux编译器的名字,-o选项指定输出文件名。到这里,程序与嵌入式系统目标机没有任何关联,所以说这是在操作系统之上编程。

而在操作系统之下编程,一定要与目标机相关联,即所编的程序经过编译连接以后产生的可执行文件要下载到目标机上运行,这时所用的编译器应该是交叉编译器arm-elf-gcc,也可说是目标编译器。

北京博创公司的UP-NETARM3000实验平台使用µclinu的交叉编译器是arm-µclibc-gcc,它的命令格式、命令选项及命令的使用同arm-elf-gcc是一样的。µclibc是µclinux提供的经过裁减的操作系统的库,应用程序很大一部分是在和库函数打交道。在目标板使用操作系统的开发模式下,交叉编译环境中还需要对应该操作系统的库。开发应用程序可以先在宿主机上调通,然后用交叉编译器为目标板重新编译一遍,这样做是因为宿主机是Linux而目标板上跑的是µclinux,两个操作系统提供的应用程序接口几乎是一样的,所以程序几乎不用修改。

3.3.2 C/C++交叉编译器arm-elf-gcc

arm-elf-gcc是编译的前端程序,它通过调用其他程序来实现将程序源文件编译成目标文件。编译时它首先调用预处理程序(cpp)对输入的源程序进行处理,然后调用ccl将预处理后的程序编译成汇编代码,最后由arm-elf-gcc将汇编代码编译成目标代码。

arm-elf-gcc具有丰富的命令选项,控制编译的各个阶段,满足用户的各种编译需求。

1. 命令格式

arm-elf-gcc [options] file „

在命令arm-elf-gcc后面可跟一个或多个选项,选项之间用空格隔开,然后跟一个或多个目标文件。

例如,将hello.c 编译成目标文件 hello.o并且生成调试信息:

arm-elf-gcc –g –c –o hello.o hello.c

-52- 嵌入式Linux系统开发与应用实验教程

其中:-g 选项表示在文件中产生调试信息;-c 选项指定将输入的源文件编译成目标文件;-o 选项指定输出文件名。

2. 命令选项列表

(1)输出控制选项如下:

-c:将输入的源文件编译成目标文件; -S:将C/C++文件生成汇编文件; -o file:将输出内容存于文件file;

-pipe:在编译的不同阶段之间采用管道通信方式; -v:打印出编译过程中执行的命令;

-x language:说明文件的输入类型为language。

(2)C语言选项如下:

-ansi:支持所有ANSI C程序。

(3)警告选项如下: -w:关闭所有警告; -Wall:打开所有警告;

-Wimplicit:如果有隐含申明,显示警告信息; -Wno-implicit:不显示对隐含申明的警告。

(4)调试选项如下:

-g:该选项在文件中产生调试信息以便调试程序(调试信息的文件格式有stabs、COFF、XCOFF、DWARF)。

(5)优化选项如下: -O0:不优化; -O1:一级优化; -O2:二级优化; -O3:三级优化。

(6)预处理选项如下: -E:运行C的预处理器;

-C:在运用-E进行预处理时不去掉注释; -D macro:定义宏macro 为1; -D macro =defn:定义宏macro为defn。

第3章 Linux下应用程序开发基础 -53-

(7)汇编选项如下:

-Wa,option:将选项option传递给汇编器。

(8)搜索路径选项如下: -I dir:设置搜索路径为dir;

-I-:指定只对#include“file”有效的头文件搜索目录。

3. 源文件类型的识别

arm-elf-gcc能够自动根据文件名后缀识别文件类型。 文件名后缀和文件类型的对应关系如下: *.c:C源文件;

*.i:经过预处理后的C源文件; *.h:C头文件;

*.ii:经过预处理后的C++源文件; *.cc:C++源文件; *.cxx:C++源文件; *.cpp:C++源文件; *.C:C++源文件;

*.s:不需要预处理的汇编文件; *.S:需要预处理的汇编文件。

此外,用户可通过-x language 说明文件的输入类型,此时可以不用以上的后缀规则。例如:-x language

其中的language可为以下参数: c:C源文件; c++:C++源文件; c-header:C头文件;

cpp-output:经过预处理后的C源文件; c++ -cpp-output:经过预处理后的C++源文件; assembler:不需要预处理的汇编文件; assembler-with-cpp:需要预处理的汇编文件。 例如,编译一个不需要预处理的C程序: arm-elf-gcc –c –g –x cpp-output hello.c -x none

如果-x后面未跟任何参数,则按照文件的后缀名做相应处理。

4.命令的使用

-54- 嵌入式Linux系统开发与应用实验教程

(1)输出文件名的指定

-o file。将输出内容存于文件file,仅适用于只有一个输出文件时。例如,将hello.c编译成汇编程序并存放于文件hello.txt:

arm-elf-gcc –S –o hello.txt hello.c

(2)目标文件的生成

-c。将输入的源文件编译成目标文件。例如,将hello.c编译成hello.o: arm-elf-gcc –c –o hello.o hello.c

(3)将C/C++文件生成汇编文件

-S。将C/C++文件生成汇编文件。例如,将hello.c编译生成汇编文件hello.s: arm-elf-gcc –S –o hello.s hello.c

(4)预处理文件的生成

-E。只对源文件进行预处理并且默认输出到标准输出。例如,对hello.c进行预处理并将结果输出到屏幕:

arm-elf-gcc –E hello.c

(5)设置头文件搜索路径

头文件的引用有两种形式:一种是 # include“filename”,另一种是 # include。前一种形式的路径搜索顺序是:当前目录、指定的搜索路径;后一种形式只搜索指定路径。

① -I dir。将目录dir添加到头文件搜索目录列表的第一项。通过此选项可以使用户头文件先于系统头文件被搜索到。如果同时用-I选项添加几个目录,目录被搜索时的优先级顺序为从左到右。

例如,编译hello.c,在当前目录和/µClinux目录中搜索hello.c所包含的头文件: arm-elf-gcc –I./ -I/µClinux –c hello.c

② -I-。-I-以前用-I指定的头文件搜索目录只对 # include“file”有效,对 # include无效;-I-以后指定的头文件搜索目录对以上两种形式的头文件都有效。此外,-I-会禁止对当前目录的隐含搜索,不过用户可以通过使用“-I.”使能对当前目录的搜索。

(6)控制警告产生

用户可以使用以-W开头的不同选项对特定警告进行设定。 例如,如有隐含申明,显示警告信息: arm-elf-gcc –c -Wimplicit hello.c

(7)实现优化

第3章 Linux下应用程序开发基础 -55-

优化的主要目的是使编译生成的代码的尺寸更小、运行速度更快,但是在编译过程中随着优化级别的升高,编译器会相应消耗更多时间和内存,而且优化生成代码的执行顺序和源代码有一定出入,因此优化选项更多地用于生成固化代码,而不用于生成调试代码。

① - O1。可以部分减小代码尺寸,对运行速度有一定的提高。较多地使用了寄存器变量,提高指令的并行度。

② - O2。除了解循环、函数插装和静态变量优化,几乎包含arm-elf-gcc所有优化选项。一般在生成固化代码时使用该选项较为适宜。

③ -O3。包含- O2的所有优化,并且还包含了解循环、函数插装和静态变量优化。通常情况下,该级优化生成的代码执行速度最快,但是代码尺寸比- O2大一些。

(8)在命令行定义宏

☉-D macro:定义宏macro 为1。 ☉-D macro =defn:定义宏macro为defn。

例如,编译hello.c并且预定义宏RUN_CACHE值为1的命令行如下: arm-elf-gcc –c –D RUN_CACHE hello.c

编译hello.c并且预定义宏RUN_CACHE值为0的命令行如下: arm-elf-gcc –c –D RUN_CACHE=0 hello.c

3.3.3 交叉连接器arm-elf-ld

arm-elf-ld根据链接定位文件Linkcmds中代码段、数据段、BBS段和堆栈段等定位信息,将可重定位的目标模块链接成一个单一的、绝对定位的目标程序,该目标程序是ELF格式,并且可以包含调试信息。arm-elf-ld可以输出一个内存映像文件,该文件显示所有目标模块、段和符号的绝对定位地址,它也产生目标模块对全局符号引用的交叉参考列表。

1. 命令格式

arm-elf-ld的命令格式如下: arm-elf-ld [option] file „

命令行后跟选项和可重定位的目标文件名。

例如,若链接的输入文件为ming.o,输出文件为ming.elf,链接的库为libxxx.a,生成内存映像文件map.txt,链接定位文件为linkcmds,则命令如下:

arm-elf-ld –Map map.txt –N –T linkcmds –L . /lib –o ming.elf ming.o -lxxx

2. 命令选项列表

arm-elf-ld的命令选项列表如下: -e entyr:指定程序入口;

-56- 嵌入式Linux系统开发与应用实验教程

-Map:指定输出映像文件; -M:输出链接信息; -lar:指定链接库; -L dir:添加搜索路径; -o:设置输出文件名;

-Tcommandfile:指定链接命令文件; -v:显示版本信息。

3.命令使用

(1) 程序入口地址

-e entry是以符号entry作为程序执行的入口地址,而不从默认的入口地址开始。 例如,链接的输入文件为ming.o,输出文件为ming.elf,链接定位文件为linkcmds,将入口地址设为_start,命令如下:

arm-elf-ld -T linkcmds –e_start –o ming.elf ming.o

(2)输出链接信息

①-M。在标准端口打印出符号映像表和内存分布信息。

例如,链接的输入文件为ming.o,输出文件为ming.elf,在标准端口打印出符号映像表和内存分布信息,命令如下:

arm-elf-ld –M –o ming.elf ming.o

如果标准输出设置为显示器,运行命令后将在显示器上显示内存映像信息和符号映像表。 ②-Map mapfile。将链接的符号映像表和内存分布信息输出到文件mapfile里。 例如,链接的输入文件为ming.o,输出文件为ming.elf,将链接的符号映像表和内存分布信息输出到文件map.txt里,命令如下:

arm-elf-ld -Map map.txt –o ming.elf ming.o

(3)指定链接的库

-lar。用于指定库文件libar.a为链接的库。可以重复使用-1来指定多个链接的库。 例如,链接的输入文件为ming.o,指定libxxx.a为链接的库,输出文件为ming.elf,命令如下:

arm-elf-ld –o ming.elf ming.o –1xxx

注意:库的命名规则为libxxx.a,在-1指定库名时使用的格式为-1xxx。

(4)添加库和脚本文件的搜索路径

-Ldir。用于将dir添加到搜索路径。搜索顺序按照命令行中输入的顺序,并且优先于默认的搜索路径。所有在-L添加的目录中找到的-1指定的库都有效。

第3章 Linux下应用程序开发基础 -57-

例如,链接的输入文件为ming.o,输出文件为ming.elf,将/lib添加到库的搜索路径,命令如下:

arm-elf-ld -L. /lib –o ming.elf ming.o

(5)设置输出文件的名字

-o output。用于将输出文件名字设定为output。如果不指定输出文件名,arm-elf-ld生成文件名默认为a.out。例如,链接的输入文件为ming.o,输出文件为ming.elf,命令如下:

arm-elf-ld –o ming.elf ming.o

4.linkcmds链接命令文件

arm-elf-ld的命令语言是一种描述性的脚本语言,它主要用于控制:有哪些输入文件、文件的格式怎样、输出文件中的模块怎样布局、分段的地址空间怎样分布等。

用命令语言写成的文件,通常称为linkcmds,它具有可重性,不必每次在命令行输入一大堆命令选项。并且对于不同的应用,只需对linkcmds进行简单的修改就可以使用。

(1)linkcmds的调用

首先写一个链接命令文件linkcmds,然后在arm-elf-ld的命令中使用-T linkcmds参数,就能在链接时自动调用linkcmds文件。

例如,链接的输入文件为ming.o,输出文件为ming.elf,链接定位文件为linkcmds,则命令如下:

arm-elf-ld –T linkcmds –o ming.elf ming.o

(2)linkcmds的编写

arm-elf-ld命令语言是一系列语句的集合,包括用简单的关键字设定选项、描述输入文件及其格式、命名输出文件。其中有两种语句对于链接过程起重要作用:SECTIONS语句和MEMORY语句。SECTIONS语句用于描述输出文件中的模块怎样布局,MEMORY语句描述目标机中可以用的存储单元。

在linkcmds中的表达式与C语言中的表达式类似,它们具有如下的特征: ● 表达式的值都是unsigned long 或者long类型; ● 常数都是整数;

● 支持C语言中的操作符; ● 可以引用或者定义全局变量; ● 可以使用内建的函数。

3.3.4 交叉汇编器arm-elf-as

-58- 嵌入式Linux系统开发与应用实验教程

arm-elf-as是GCC工具链中的汇编程序编译器,针对ARM处理器并产生ELF(Executable and Linking Format 执行时链接文件格式)格式的二进制代码。

arm-elf-as产生一个交叉参考表和一个标准的符号表,产生的代码和数据能够放在多个段中。

1.命令格式

arm-elf-as 命令格式如下: arm-elf-as [option„][asmfile „]

在命令arm-elf-as后面跟一个或多个选项,以及该选项的子选项,选项间用空格隔开,然后跟汇编源文件名。例如,将ming.s编译成目标文件,并且设置头文件的搜索目录为C:\\ming\\include:

arm-elf-as -I//c/ming/include ming.s

2. 命令选项列表

命令选项列表如下:

-a[dhlns]: 显示arm-elf-as信息; -f:不进行预处理;

-I path:设置头文件搜索路径; -o:设定输出文件名; -v:显示版本信息; -W:不显示警告提示; -Z:不显示错误提示。

3.命令的使用

(1)生成目标文件

每次运行arm-elf-as只输出一个目标文件,默认状态下名字为a.out。可以通过-o选项输出文件名字。例如,编译ming.s输出目标文件ming.o:

arm-elf-as –o ming.o ming.s

(2)设置头文件搜索路径

-I path用于添加路径path到arm-elf-as的搜索路径,搜索include“file”指示的文件。-I可以被使用多次以添加多个目录,当前工作目录将最先被搜索,然后从左到右依次搜索-I指定的目录。例如,编译ming.s时指定两个搜索目录,当前目录和C:\\ming\\include:

arm-elf-as -I../ -I//c/ming/include ming.s

第3章 Linux下应用程序开发基础 -59-

(3)显示arm-elf-as信息内容

-a[dhlns]用于打开arm-elf-as信息显示。Dhlns为其子选项,分别表示如下: d:不显示调试信息; h:显示源码信息; I:显示汇编列表; n:不进行格式处理; s:显示符号列表。

在不添加子选项时,-a表示显示源码信息,显示汇编列表,显示符号列表。添加子选项时将选项直接加在-a以后可以添加一个或多个。默认状态时显示的信息输出到屏幕,也可用重定向输出到文件。例如,编译ming.s生成不进行格式处理的汇编列表,输出到文件a.txt:

arm-elf-as -aln –o ming.o ming.s>a.txt

(4)设置目标文件名字

-o filename用于控制每次运行arm-elf-as只输出一个目标文件,默认输出文件为a.out。可以通过-o选项指定输出文件名字,通常都以.o为后缀。如果指定输出文件的名字和现有某个文件重名,生成的文件将直接覆盖已有的文件。例如,编译ming.s输出目标文件ming.o:

arm-elf-as -I/include –o ming.o ming.s

(5)如何取消警告信息

-W用于控制运行arm-elf-as不输出警告信息。例如,编译ming.s输出目标文件ming.o,不输出警告信息:

arm-elf-as -W –o ming.o ming.s

(6)设置是否进行预处理

arm-elf-as内部的预处理程序,完成以下工作:调整并删除多余空格,删除注释,将字符常量该成对应的数值。

arm-elf-as不执行arm-elf-gcc预处理程序能完成的部分,如宏预处理和包含文件预处理。可以通过.include“file”对指定文件进行预处理。arm-elf-gcc可以对后缀为.s汇编出现进行其他形式的预处理。

如果源文件第一行是# NO_APP或者编译时使用选项-f将不进行预处理。如果要保留空格或注释,可以在需要保留部分开始加入# APP,结束的地方加 # NO_APP。

例如,编译ming.s输出目标文件ming.o,并且编译时不进行预处理,则命令如下: arm-elf-as -f -o ming.o ming.s

3.3.5 自动化管理工具GNU Make

GNU Make 是Linux专门为软件开发提供的一个用来控制软件构建过程的自动化管理工具。在大型的开发项目中,通常有几十到上百个的源文件,如果每次均手工键入 gcc 命令进

-60- 嵌入式Linux系统开发与应用实验教程

行编译的话,则会非常不方便。因此,人们通常利用 make 工具来自动完成编译工作。

make 工具通过一个称为 Makefile 的文件来完成并自动维护编译工作。Makefile 需要按照某种语法进行编写,其中说明了如何编译各个源文件并连接生成可执行文件,并定义了源文件之间的依赖关系。当修改了其中某个源文件时,如果其他源文件依赖于该文件,则也要重新编译所有依赖该文件的源文件。如果 Makefile 文件存在,每次修改完源程序后,用户通常所需要做的事情就是在命令行键入“make”,然后所有的事情都由make来完成。

1.命令格式

make [-f Makefile][option][targer] „

make命令后跟-f选项,指定Makefile 的名字为 Makefile;option表示make的一些选项;target是make 指定的目标。例如,Makefile的名字是my_hello_make:

make –f my_hello_make

2. 命令选项列表

命令选项列表如下:

-f FILE: 以指定的 FILE 文件作为 Makefile; -e:使环境变量优先于 Makefile的变量; -I dir:设定搜索目录; -i:忽略所有的命令执行错误;

-n: 只打印要执行的命令,但不执行这些命令; -p:显示 make 变量数据库和隐含规则;

-w: 在处理 Makefile 之前和之后,显示工作目录; -s:在执行命令时不显示命令; -r:使隐含规则无效; -h:显示所有的 make 选项;

-C dir: 读取 Makefile 设置的工作目录 。

3.命令使用

Makefile 文件用来告诉make需要做的事情,通常指怎样编译、怎样链接一个程序。以C语言程序为例,在用make重新编译的时候,如果一个头文件已被修改,则包含这个头文件的所有C源代码文件都必须被重新编译。而每个目标文件都与C的源代码文件有关,如果有源代码文件被修改过,则所有目标文件都必须被重新链接生成最后的结果。

(1) 指定Makefile

第3章 Linux下应用程序开发基础 -61-

-f Makefile 。用该选项指定Makefile 的名字为Makefile 。如果make中多次使用-f指定多个Makefile ,则所有Makefile 将链接起来作为最后的Makefile 。如果不指定Makefile ,make默认的Makefile 依次为“Makefile ”、“Makefile”。例如:

make -f my_hello_make

(2)使环境变量优先于Makefile 文件中的变量 -e 。使环境变量优先于Makefile 文件中的变量。例如: make -e

(3)指定包含文件的搜索路径

-I dir。指定在解析Makefile文件中的.include时的搜索路径为dir。如果有多个路径,将按输入顺序依次查找。例如:

make -I/include/mk

(4)忽略错误

-I 。忽略make执行过程中的所有错误。例如: make -i

(5)显示命令的执行过程

-n。只显示命令的执行过程而不真正执行。例如: make –n

(6)使隐含规则无效

-r。使make的隐含规则无效,清除后缀名规则中默认的后缀清单。例如: make –r

(7)显示执行过程中的工作目录

-w。显示make执行过程中的工作目录。例如: make -w

(8)读取Makefile文件前设置工作目录

-C dir。在读取Makefile文件以前将工作目录改变为 dir,完成 make后改回原来的目录。如果在一次 make中使用多个-C选项,每个选项都和前面一个有关系。“-C dir0 / -C dir1”与“-C dir0 /dir1”等价。例如:

make -C bsp

(9)不显示所执行的命令

-s。运行make时用–s可以不显示执行的命令,只显示生成的结果文件。例如:

-62- 嵌入式Linux系统开发与应用实验教程

make -s

4.编写一个Makefile

(1)Makefile的结构

Makefile文件一般包含如下内容:

● 需要由 make 工具创建的项目,通常是目标文件和可执行文件; ● 要创建的项目依赖于哪些文件; ● 创建每个项目时需要运行的命令; ● 变量的定义; ● 注释。

(2)编写Makefile文件的规则 Makefile中规则的格式如下: targers:dependencies ┇ 或者

targers:dependencies;command ┇

targers指定目标名,通常是一个程序产生的目标文件名,也可能是执行一个动作的名字,名字之间用空格隔开。dependencies描述产生targers所需的文件,一个targers通常依赖于多个dependencies。Command用于指定该规则的shell命令,Shell命令可以有若干行。

注意:command必须以Tab键开头。否则,make就会显示出错信息。如果某一命令行太长可以分作两行,用反斜杠(\\)连接。

举例,用vi编辑器输入如下Makefile文件: vi target ↙ test:prog.o code.o

gcc –o test prog.o code.o gcc –c prog.c –o prog.o gcc –c code.c –o code.o rm –f *.o

prog.o:prog.c prog.h code.h code.o:code.c code.h clean:

command command

第3章 Linux下应用程序开发基础 -63-

:wq ↙ (存盘退出)

在上面的Makefile文件定义了4个目标:test、prog.o、code.o和clean。每个目标都是从最左边开始写,后面跟一个(:)冒号,如果这个目标的实现依赖于其他的目标或文件,则把他们列在冒号的后面,并以空格隔开。然后另起一行开始写实现这个目标的一组shell命令,命令必须以Tab键开头。

(3)make 调用Makefile中的规则 一般情况下,调用make命令可输入: # make target

target是Makefile文件中定义的目标之一,如果省略target,make就将生成Makefile文件中定义的第一个目标。对于上面Makefile的例子,make默认执行的是规则test,因为test是Makefile文件中定义的第一个目标,此时只需要输入命令:

# make

make将读入Makefile,然后执行第一条规则,例子中该规则是链接目标文件生成库,因此必须执行规则test依赖的规则prog.o和 code.o。在执行过程中将自动更新他们所依赖的文件。

有些规则不是被依赖的规则,需要make指定才能被运行,如上面例子中的clean规则可以这样执行:

make clean

这两种方式的结果一样,只是第一种方式没指明目标名,第二种方式指明了目标名。 在Makefile中,并不是所有的目标都对应于磁盘上的文件,有的目标存在只是为了形成一条规则,从而完成特定的工作,并不生成新的目标文件,这样的目标称为伪目标。Clean就是伪目标,Clean目标一般用于删除最终生成的可执行文件和在编译过程中产生的所有目标文件。

make在检查一个目标是否已经过时并需要更新时,采用的是按相关性递归的方法。Make在构建一个目标之前要生成该目标所依赖的所有文件,并递归地前进,从而确保这些文件都是新的。

(4)设置Makefile中文件的搜索路径

在Makefile中,可以通过给VPATH赋值来设置规则中目标文件和依赖文件的搜索目录。Make首先搜索当前目录,如果未找到依赖的文件,make将按照VPATH中给的目录依次搜索。VPATH对Makefile中所有文件都有效。例如:

ming.o: ming.c ming.h

ming.c 在目录//c/ming/ 中,ming.h 在目录//c/ming/head/中,则可以给VPATH变量赋值: VPATH:=//c/ming //c/ming/head 或

VPATH:=//c/ming: //c/ming/head

-- 嵌入式Linux系统开发与应用实验教程

也可以使用指令vpath,与VPATH在使用上的区别是:vpath可以给不同类文件指定不同的搜索目录。% .o表示所有以 .o结尾的子串。

vpath %.c //c/ming vpath %.h //c/ming/head

vpath %.c表示清除所有vpath对%.c设置的搜索目录 vpath表示清除所有以前用vpath设置的搜索目录

这两种方式的效果是一样的,但是后一种要明确一些。这样make就会根据VPATH或者vpath来搜索相应的依赖文件。

(5)定义变量和引用变量

为了简化Makefile的编写,make引入了变量。变量实际上是为文本串在Makefile中定义一个便于记忆的名称。变量的定义和应用与Linux环境变量一样,变量名要大写,变量一旦定义之后,就可以通过将变量名用圆括号括起来,并在前面加上“$”符号来进行引用。

变量的主要作用如下:

● 保存文件名列表。在前面的例子里,作为依赖文件的一些目标文件名出现在可执行文件的规则中,而在这个规则的命令行里同样包含这些文件并传递给gcc做为命令参数。如果使用一个变量来保存所有的目标文件名,则可以方便地加入新的目标文件而且不易出错。

● 保存可执行命令名,如编译器。在不同的Linux系统中存在着很多相似的编译器 系统,这些系统在某些地方会有细微的差别,如果项目被用在一个非gcc的系统里,则必须将所有出现编译器名的地方改成用新的编译器名。但是如果使用一个变量来代替编译器名,那么只需要改变该变量的值。其他所有地方的命令名就都改变了。

● 保存编译器的参数。在很多源代码编译时,gcc需要很长的参数选项,在很多情况下,所有的编译命令使用一组相同的选项,如果把这组选项使用一个变量代表,那么可以把这个变量放在所有引用编译器的地方。当要改变选项的时候,只需改变一次这个变量的内容即可。

变量一般都在Makefile的头部定义。如果变量的值发生了改变,只需在一个地方进行修改就可以了,从而大大简化了Makefile的维护。按照惯例,所有的Makefile变量都应该是大写。现在利用变量把前面的Makefile重写一遍:

OBJS=prog.o code.o CC=gcc test:${ OBJS }

${ CC } –o test ${ OBJS } ${ CC } –c prog.c –o prog.o ${ CC } –c code.c –o code.o rm –f *.o

prog.o:prog.c prog.h code.h code.o:code..c code.h clean:

第3章 Linux下应用程序开发基础 -65-

除了用户自定义变量之外,在Makefile中还可以使用环境变量、自动变量和预定义变量。make 在启动时会自动读取系统当前已经定义了环境变量,并且会创建与之具有相同名称和数值的变量。需要注意的是,如果用户在Makefile中定义了相同名称的变量,那么用户自定义变量将会覆盖同名的环境变量。Make提供的预定义变量和自动变量具有特殊的含义,可在规则中使用。表 3-1 给出了一些主要的预定义变量,除这些变量外, make 还将所有的环境变量作为自己的预定义变量。

表 3-1 GNU make 的主要预定义变量

预定义变量 $* $+ $< $? $@ $^ $%

(6)Makefile的隐含规则

在上面的例子中,几个产生目标文件的命令都是从―.c‖的C语言源文件和相关文件通过编译产生―.o‖目标文件,这也是一般的步骤。实际上,make可以使工作更加自动化,也就是说,make知道一些默认的动作,它有一些称作隐含规则的内置的规则,这些规则告诉make当用户没有完整地给出某些命令的时候,应该怎样执行。

例如,把生成prog.o和code.o的命令从规则中删除,make将会查找隐含规则,然后会找到并执行一个适当的命令。由于这些命令会使用一些变量,因此可以通过改变这些变量来定制make。象在前面的例子中所定义的那样,make使用变量CC来定义编译器,并且传递变量CFLAGS(编译器参数)、CPPFLAGS(C语言预处理器参数)、TARGET_ARCH(目标机器的结构定义)给编译器,然后加上参数-c,后面跟变量$<(第一个依赖文件名),然后是参数-o加变量$@(目标文件名)。综上所述,一个C编译的具体命令将会是:

$ {CC} $ {CFLAGS} $ {CPPFLAGS} $ {TARGET_ARCH} –c $< -o $@

在上面的例子中,利用隐含规则,可以简化为: OBJS=prog.o code.o CC=gcc

含 义 不包含扩展名的目标文件名称。 所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件。 第一个依赖文件的名称。 所有的依赖文件,以空格分开,这些依赖文件的修改日期比目标的创建日期晚。 目标的完整名称。 所有的依赖文件,以空格分开,不包含重复的依赖文件。 如果目标是归档成员,则该变量表示目标的归档成员名称。例如,如果目标名称为 mytarget.so(image.o),则 $@ 为 mytarget.so,而 $% 为 image.o。 -66- 嵌入式Linux系统开发与应用实验教程

test:${ OBJS }

${ CC } –o $@ $^ prog.o:prog.c prog.h code.h code.o:code.c code.h clean:

再举例说明在我们的实验开发板上如何编写hello应用程序的Makefile : CC=arm-uclibc-gcc EXEC = hello OBJS = hello.o CFLAGS +=

LDFLAGS+=-elf2flt –static

all: $(EXEC) $(EXEC): $(OBJS)

$(CC) $(LDFLAGS) -o $@ $(OBJS) clean:

-rm -f $(EXEC) *.elf *.gdb *.o

这个makefile 显示了几个主要的部分:

◆ CC 指明编译器的宏。

◆ EXEC 表示生成的执行文件名称的宏。 ◆ OBJS 目标文件列表宏。 ◆ CFLAGS 编译参数宏。 ◆ LDFLAGS 连接参数宏。 ◆ all: 编译主入口。 ◆ clean:清除编译结果节。

rm –f *.o

3.3.6 调试工具GDB

Linux包含了一个叫GDB的GNU调试程序,GDB是一个用来调试C/C++程序的强力调试器,它使你能在程序运行时观察程序的内部结构和内存的使用情况,它可完成如下一些调试任务:

● 设置断点; ● 监视程序变量的值;

第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 \"show copying\" to see the conditions.

There is absolutely no warranty for GDB. Type \"show warranty\" for details. This GDB was configured as \"i386-redhat-linux-gnu\". (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 #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 可移植性编程

-72- 嵌入式Linux系统开发与应用实验教程

对于嵌入式系统,不同的运行平台可能要求不同的程序代码来实现它所要求的独特功能。为了增加程序代码可移植到多个不同平台的可行性,比较好的方法是提供一个可移植的数据或功能接口,让那些不可移植的部分隐藏在这些接口之后。虽然这些事情应该是系统设计的工作,但我们仍然希望通过以下的介绍,帮助程序员了解这些技术,并且在实际工作中考虑到这一点。

1.数据大小或长度相关性

C程序库提供的“sizeof()”函数是一个很好的可移植的功能接口范例,对于不同的嵌入式系统的编译环境或平台,某些数据类型的大小或长度被解析成不一样的结果。而在程序体中,对这些数据类型的访问又有十分严格的要求。所以在这种情况下,对这些数据类型的定义必须考虑到在不同环境的共享,也就是说,数据类型的定义将成为可移植的数据接口。例如,程序中有对8位、16位和32位的整数类型数据进行访问的要求,为了增加程序代码的可移植性,惯长的作法是把这些数据以全局类型定义在某个H头文件中。参见表3-2。

表3-2 可移植性编程范例1

范例: typedef signed char int8 typedef unsigned char uint8; typedef signed int int16; typedef unsigned int uint16; typedef signed long int32; typedef unsigned long uint32;

2. 字节位序

不同的CPU,对字节顺序的解析是不同的。也就是说,对高字节在前还是低字节在强,它们的处理方法是截然不同的。着是有CPU 内部寄存器的存储和访问机制决定的,也是我们常说的“Big-Byte-Endian”和“Little-Byte-Endian”。这样的特点对程序中的字节和位的操作将会有相当大的影响,所以我们建议将涉及位操作的程序设计成仅仅与固定的位序相关,变量或类型同样也定义成与CPU相关的数据接口。参见表3-3。

表3-3 可移植性编程范例2

范例(在H头文件中的可移植的数据接口定义) typedef struct { 第3章 Linux下应用程序开发基础 -73-

# if littleEndian word hiword; word loword; # else word loword; word hiword; # endif } Dword; 在C源文件中的应用: Dword ABC; Word *ImgPtr; ┉ ABC.hiword = *Imgprt ++ ; ABC.loword = *Imgprt ++;

3.位操作(bit operation)和对齐(alignment)

在嵌入式系统开发中,基于存储空间(SDRAM或NVRAM等)的,我们经常利用位(bit)来表示某些设备或操作的状态,也就是说,位操作是一种使用十分频繁并高效的操作。同样,位序(Bit Ordering)也和CPU是相关的,所以我们习惯上将位的定位定义为一些宏,从而提高它们的可移植性。参见表3-4。

表3-4 可移植性编程范例3

范例(在H头文件中的可移植的数据接口定义): # define BYTE_BIT0 0x01 # define BYTE_BIT1 0x02 # define BYTE_BIT2 0x04 # define BYTE_BIT3 0x08 # define BYTE_BIT4 0x10 # define BYTE_BIT5 0x20 # define BYTE_BIT6 0x40 # define BYTE_BIT7 0x80

-74- 嵌入式Linux系统开发与应用实验教程

对齐(alignment)同样也与CPU紧密相关,ARM是一个32位处理器,通常,如果没有特殊的要求,C语言中的结构体(struct)要求4字节对齐,也就是说,ARM不支持非对齐字节的数据传输,在C语言里,一旦使用了32位的指针操作一个非4字节对齐的数据,ARM就会陷入一个异常。字节对齐问题的解决是通过使用紧缩数据相关的关键字,让C语言的结构体以紧缩的方式存储。

4.断言的使用

在嵌入式系统开发中,程序一般会分为DEBUG版本和RELEASE版本,DEBUG版本仅仅用于开发工程的内部调试,RELEASE版本是最终向用户发行的版本。断言(ASSERT)是仅用于在DEBUG版本中起调试作用的宏(不是一个函数),它用于检查、捕捉不应该发生的非法情况。如果在编写程序时,已有很好的断言设计和实现,至少可以帮助我们发现与环境和硬件相关的问题,从而找到好的解决方案。

本 章 小 结

本章介绍了嵌入式Linux应用软件开发的相关知识。重点介绍了开发工具的使用,其中包括全屏编辑器vi、集成开发工具KDevelop;还较详尽地介绍了交叉编译工具链GUN GCC中的编译器、连接器、汇编器、调试器得使用;通过实例介绍了Makefile文件的编写及调用;在第4小节对嵌入式应用软件的编程风格进行了描述。这些是进行嵌入式应用与开发所必备的知识。通过本章的学习和实践,熟练运用开发工具完成嵌入式系统的实验和软件开发工作。

思 考 题

1. 何为交叉编译?交叉编译与一般的程序编译有什么不同? 2. Makefile文件的编写规则和调用规则是什么? 3. 试编写一个Makefile文件。

4. 用vi编辑器输入一个C源程序,用gcc进行编译,然后用gdb进行调试。 5. 编程中需要注意的事项都有那些?

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- baoaiwan.cn 版权所有 赣ICP备2024042794号-3

违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务