【VC++开源代码栏目提醒】:网学会员,鉴于大家对VC++开源代码十分关注,论文会员在此为大家搜集整理了“GNU工具链 - 自学考试”一文,供大家参考学习!
GNU GNU 是由“GNUs Not Unix”所递规定义出的首字母缩写语。
GNU计划是由Richard Stallman在1983年9月27日公开发起的。
它的目标是创建一套完全自由的操作系统。
Richard Stallman最早是在net.unix-wizards新闻组上公布该消息,并附带一份《GNU宣言》解释为何发起该计划的文章,其中一个重要的理由就是要―重现当年软件界合作互助的团结精神‖。
GNU工程已经开发了一个被称为“GNU”的、对Unix向上兼容的完整的自由软件系统free software system。
由Richard Stallman完成的最初的GNU工程的文档被称为“GNU宣言”。
每个计算机的使用者都需要一个操作系统;如果没有自由的操作系统,那么如果他不求助于私有软件,就甚至不能开始使用一台计算机。
所以自由软件议事日程的第一项就是自由的操作系统。
一个操作系统不仅仅是一个内核;它还包括编译器、编辑器、电子邮件软件,和许多其他东西。
因此,创作一个完整的操作系统是一项十分庞大的工作。
由于Unix的全局设计已经得到认证并且广泛流传,所以GNU开发者们决定使操作系统与Unix兼容。
同时这种兼容性使Unix的使用者可以容易地转移到GNU上来。
在1991年Linux的第一个版本公开发行时,GNU计划已经完成除操作系统内核之外的大部分软件,比如GNU Bash,GCC等等。
于是Linus的内核和GNU软件的合理组合,构成了GNU/Linux这一优异的操作系统:一个基于Linux的GNU系统。
GCCGNU Compiler Collection是GNU组织开发的一个编译器。
目前支持的语言有 C、C、Objective-C、Fortran、Java和Ada等。
自由软件可以走多远?这没有限制,除非诸如版权法之类的法律完全地禁止自由软件。
最终的目的是,让自由软件完成计算机用户希望完成的所有工作--从而导致自由软件的过时。
Linux系统下的GCC是GNU推出的功能强大、性能优越的多平台编译器,是GNU的代表作品之一。
GCC是可以在多种硬体平台上编译出可执行程序的超级编译器,其执行效率与一般的编译器相比平均效率要高2030。
最初,GCC只是一个C语言编译器,当时是“GNU C Compiler”的英文缩写。
随着众多开发者的加入和GCC自身的发展,如今的GCC已经是一个包含众多语言的编译器了,其中包括 C,C,Ada,Object-C和Java等。
所以,GCC的全称也由原来的“GNU C Compiler”演变为现在的“GNU Compiler Collection”,即GNU编译器家族的意思。
GCC不仅是GNU/Linux上的标准编译器,而且它也是嵌入式系统开发的标准编译器,这是因为GCC支持各种不同的目标架构。
本书将专注于FPGA平台的嵌入式系统开发,其中的软件部分运行在Microblaze或者PowerPC处理器上,为了使我们的应用程序能够运行在不同的目标机上,我们使用交叉编译工具对程序进行交叉编译。
所谓交叉编译就是在某个主机平台上(比如PC上)编译出可在其他平台上(比如ARM上)运行
代码的过程。
GCC提供了40种不同的结构体系。
其中包括X86,RS6000,Arm,PowerPC等等,用户可以根据实际的项目平台来进行应用程序的开发。
GCC编译器能将C、C语言源程序、汇编程序和目标程序编译、链接成可执行文件,如果没有给出可执行文件的名字,GCC将生成一个名为a.out的文件。
在Linux系统中,可执行文件没有统一的后缀,系统从文件的属性来区分可执行文件和不可执行文件。
而GCC则通过后缀来区别输入文件的类别,下面我们来介绍GCC所遵循的部分约定规则: .c为后缀的文件,C语言源
代码文件; .a为后缀的文件,是由目标文件构成的档案库文件; .C、.cc或.cxx 为后缀的文件,是C源
代码文件; .h为后缀的文件,是程序所包含的头文件; .i 为后缀的文件,是已经预处理过的C源
代码文件; .ii为后缀的文件,是已经预处理过的C源
代码文件; .m为后缀的文件,是Objective-C源
代码文件; .o为后缀的文件,是编译后的目标文件; .s为后缀的文件,是汇编语言源
代码文件; .S为后缀的文件,是经过预编译的汇编语言源
代码文件。
虽然我们称GCC是C语言的编译器,但使用GCC由C语言源
代码文件生成可执行文件的过程不仅仅是编译的过程,而是要经历四个相互关联的步骤∶预处理也称预编译,Preprocessing、编译Compilation、汇编Assembly和链接Linking,如图2-1所示。
在对程序进行开发的过程中,我们可以通过添加参数对程序单独执行其中的某个过程。
图2-1 GCC执行过程 命令GCC首先调用cpp进行预处理,在预处理过程中,对源
代码文件中的文件包含include、预编译语句如宏定义define等进行分析。
接着调用cc1或g进行编译,这个阶段根据输入文件生成以.o为后缀的目标文件。
汇编过程是针对汇编语言的步骤,调用as进行工作,一般来讲,.s为后缀的汇编语言文件经过预编译和汇编之后都生成以.o为后缀的目标文件。
当所有的目标文件都生成之后,GCC就调用ld命令来完成最后的关键性工作,这个阶段就是链接,当然,也可以使用GCC命令直接完成链接功能。
在链接阶段,所有的目标文件被安排GCC cpp cc1 asassembler ldlinker g 在可执行程序中的恰当的位置,同时,该程序所调用到的库函数也从各自所在的档案库中连到合适的地方。
在使用GCC编译器的时候,我们必须给出一系列必要的调用参数和文件名称。
GCC编译器的调用参数大约有100多个,但其中多数参数很少会用到,所以这里只介绍其中最基本、最常用的参数。
GCC最基本的用法是∶gcc options filenames ,其中options就是编译器所需要的参数,filenames给出相关的文件名称,表2-1列出了常用参数的意义。
选项 解释 -ansi 只支持 ANSI 标准的 C 语法 -c 只编译并生成目标文件 -DMACRO 以字符串“1”定义 MACRO 宏 -DMACRODEFN 以字符串“DEFN”定义 MACRO 宏 -E 只运行 C 预编译器 -g 生成调试信息 -IDIRECTORY 指定额外的头文件搜索路径DIRECTORY -LDIRECTORY 指定额外的函数库搜索路径DIRECTORY -lLIBRARY 连接时搜索指定的函数库LIBRARY -o FILE 生成指定的输出文件 -O0 不进行优化处理 -O 或 -O1 优化生成
代码 -O2 进一步优化 -O3 比 -O2 更进一步优化,包括 inline 函数 -static 禁止使用共享连接 -w 不生成任何警告信息 -Wall 生成所有警告信息 表2-1 GCC常用参数 上面我们简要介绍了GCC编译器最常用的功能和主要参数选项,更为详尽的资料可以参考http://gcc.gnu.org/。
假定我们有一个程序名为test.c的C语言源
代码文件,要生成一个可执行文件,最简单的办法就是: gcc test.c 这时,预编译、编译链接一次完成,生成一个系统预设的名为a.out的可执行文件,对于稍为复杂的情况,比如有多个源
代码文件、需要链接档案库或者有其他比较特别的要求,就要给定适当的调用选项参数。
再看一个简单的例子。
整个源
代码程序由两个文件test1.c 和test2.c组成,程序中使用了系统提供的数学库,同时希望给出的可执行文件为test,这时的编译命令可以是∶ gcc test1.c test2.c -lm -o test 其中,-lm表示链接系统的数学库libm.a。
调试是所有程序员都会面临的问题。
如何提高程序员的调试效率,更好更快的定位程序中的问题从而加快程序开发的进度,是大家共同面对的。
就如读者熟知的Windows下的一些调试工具,如
VC自带的如设置断点、单步跟踪等,都受到了广大用户的赞赏。
那么,在Linux下有什么很好的调试工具呢? Gdb调试器是一款GNU开发组织并发布的UNIX/Linux下的程序调试工具。
虽然,它没有图形化的友好界面,但是它强大的功能也足以与微软的
VC工具等媲美。
首先,打开Linux下的编辑器Vi或者Emacs,编辑如下
代码。
/test.c/ include ltstdio.hgt int sumint m int main int in0 sum50 fori1 ilt50 i n i printfquotThe sum of 1-50 is d nquot n int sumint m int in0 fori1 iltmi n i printfquotThe sum of 1-m is dnquot n 在保存退出后首先使用Gcc对test.c进行编译,注意一定要加上选项”-g”,这样编译出的可执行
代码中才包含调试信息,否则之后Gdb无法载入该可执行文件。
rootlocalhost Gdb gcc -g test.c -o test 虽然这段程序没有错误,但调试完全正确的程序可以更加了解Gdb的使用流程。
接下来就启动Gdb进行调试。
注意,Gdb进行调试的是可执行文件,而不是如”.c”的源
代码,因此,需要先通过Gcc编译生成可执行文件才能用Gdb进行调试。
rootlocalhost Gdb gdb test GNU Gdb Red Hat Linux 6.3.0.0-1.21rh Copyright 2004 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 quotshow copyingquot to see the conditions. There is absolutely no warranty for GDB. Type quotshow warrantyquot for details. This GDB was configured as quoti386-redhat-linux-gnuquot...Using host libthread_db library quot/lib/libthread_db.so.1quot. gdb 可以看出,在Gdb的启动画面中指出了Gdb的版本号、使用的库文件等信息,接下来就进入了由“(gdb)”开头的命令行界面了。
(1)查看文件 在Gdb中键入”l”(list)就可以查看所载入的文件,如下所示: Gdb l 1 include ltstdio.hgt 2 int sumint m 3 int main 4 5 int in0 6 sum50 7 fori1 ilt50 i 8 9 n i 10 Gdb l 11 printfquotThe sum of 1~50 is d nquot n 12 13 14 int sumint m 15 16 int in0 17 fori1 iltmi 18 n i 19 printfquotThe sum of 1~m is dnquot n 20 可以看出,Gdb列出的源
代码中明确地给出了对应的行号,这样就可以大大地方便
代码的定位。
(2)设置断点 设置断点是调试程序中是一个非常重要的手段,它可以使程序到一定位置暂停它的运行。
因此,程序员在该位置处可以方便地查看变量的值、堆栈情况等,从而找出
代码的症结所在。
在Gdb中设置断点非常简单,只需在”b”后加入对应的行号即可(这是最常用的方式,另外还有其他方式设置断点)。
如下所示: Gdb b 6 Breakpoint 1 at 0x804846d: file test.c line 6. 要注意的是,在Gdb中利用行号设置断点是指
代码运行到对应行之前将其停止,如上例中,
代码运行到第五行之前暂停(并没有运行第五行)。
(3)查看断点情况 在设置完断点之后,用户可以键入”info b”来查看设置断点情况,在Gdb中可以设置多个断点。
Gdb info b Num Type Disp Enb Address What 1 breakpoint keep y 0x0804846d in main at test.c:6 (4)运行
代码 接下来就可运行
代码了,Gdb默认从首行开始运行
代码,可键入”r”(run)即可(若想从程序中指定行开始运行,可在r后面加上行号)。
Gdb r Starting program: /root/workplace/Gdb/test Reading symbols from shared object read from target memory...done. Loaded system supplied DSO at 0x5fb000 Breakpoint 1 main at test.c:6 6 sum50 可以看到,程序运行到断点处就停止了。
(5)查看变量值 在程序停止运行之后,程序员所要做的工作是查看断点处的相关变量值。
在Gdb中只需键入”p”+变量值即可,如下所示: Gdb p n 1 0 Gdb p i 2 134518440 在此处,为什么变量”i”的值为如此奇怪的一个数字呢?原因就在于程序是在断点设置的对应行之前停止的,那么在此时,并没有把”i”的数值赋为零,而只是一个随机的数字。
但变量”n”是在第四行赋值的,故在此时已经为零。
(6)单步运行 单步运行可以使用命令”n”(next)或”s”(step),它们之间的区别在于:若有函数调用的时候,”s”会进入该函数而”n”不会进入该函数。
因此,”s”就类似于
VC等工具中的”step in”,”n”类似与
VC等工具中的”step over”。
它们的使用如下所示: Gdb n The sum of 1-m is 1275 7 fori1 ilt50 i Gdb s sum m50 at test.c:16 16 int in0 可见,使用”n”后,程序显示函数sum的运行结果并向下执行,而使用”s”后则进入到sum函数之中单步运行。
(7)恢复程序运行 在查看完所需变量及堆栈情况后,就可以使用命令”c”(continue)恢复程序的正常运行了。
这时,它会把剩余还未执行的程序执行完,并显示剩余程序中的执行结果。
以下是之前使用”n”命令恢复后的执行结果: Gdb c Continuing. The sum of 1-50 is :1275 Program exited with code 031. 可以看出,程序在运行完后退出,之后程序处于“停止状态”。
到此为止,我们已经了解了如何在Linux下使用编辑器编写
代码,如何使用Gcc把
代码编译成可执行文件,还学习了如何使用Gdb来调试程序,那么,所有的工作看似已经完成了,为什么还需要Make这个工程管理器呢? 所谓工程管理器,顾名思义,是指管理较多的文件的。
可以试想一下,有一个上百个文件的
代码构成的项目,如果其中只有一个或少数几个文件进行了修改,按照之前所学的Gcc编译工具,就不得不把这所有的文件重新编译一遍,因为编译器并不知道哪些文件是最近更新的,而只知道需要包含这些文件才能把源
代码编译成可执行文件。
于是,程序员就不能不再重新输入数目如此庞大的文件名以完成最后的编译工作。
但是,仔细回想一下本书在2.2.3节中所阐述的编译过程,编译过程是分为编译、汇编、链接不同阶段的,其中编译阶段仅检查语法错误以及函数与变量的声明是否正确声明了,在链接阶段则主要完成是函数链接和全局变量的链接。
因此,那些没有改动的源
代码根本不需要重新编译,而只要把它们重新链接进去就可以了。
所以,人们就希望有一个工程管理器能够自动识别更新了的文件
代码,同时又不需要重复输入冗长的命令行,这样,Make工程管理器也就应运而生了。
实际上,Make工程管理器也就是个“自动编译管理器”,这里的“自动”是指它能够根据文件时间戳自动发现更新过的文件而减少编译的工作量,同时,它通过读入Makefile文件的内容来执行大量的编译工作。
用户只需编写一次简单的编译语句就可以了。
它大大提高了实际项目的工作效率,而且几乎所有Linux下的项目编程均会涉及到它。
makefile描述了整个工程的编译规则通过make命令自动化编译。
Make是一个解释makefile 中指令的命令工具,大多数的IDE都有这个命令,比如: ?? Delphi的make, ?? Visual C的nmake ?? Linux下GNU的make Makefile是Make读入的惟一配置文件,因此本节的内容实际就是讲述Makefile的编写规则。
在一个Makefile中通常包含如下内容: · target:是一个目标文件,可以是Object File,也可以是可执行文件,还可以是一个标签(Label); · prerequisites:要生成那个target所需要的文件或是目标; · Command:make需要执行的命令 。
它的格式为: Target:prerequisites Command 例如,有两个文件分别为hello.c和hello.h,创建的目标体为hello.o,执行的命令为gcc编译指令:gcc –c hello.c,那么,对应的Makefile就可以写为: hello.o: hello.c hello.h gcc –c hello.c –o hello.o 接着就可以使用make了。
使用make的格式为:make target,这样make就会自动读入Makefile(也可以是首字母小写makefile)并执行对应target的command语句,并会找到相应的依赖文件。
如下所示: rootlocalhost makefile make hello.o gcc –c hello.c –o hello.o rootlocalhost makefile ls hello.c hello.h hello.o Makefile 可以看到,Makefile执行了“hello.o”对应的命令语句,并生成了“hello.o”目标体。
tab8spacemake 上面示例的Makefile在实际中是几乎不存在的,因为它过于简单,仅包含两个文件和一个命令,在这种情况下完全不必要编写Makefile而只需在Shell中直接输入即可,在实际中使用的Makefile往往是包含很多的文件和命令的,这也是Makefile产生的原因。
下面就可给出稍微复杂一些的Makefile(2个头文件,5个C文件)进行讲解: edit : main.o kbd.o cc -o edit main.o kbd.o main.o : main.c defs.h cc -c main.c kbd.o : kbd.c defs.h command.h cc -c kbd.c clean : rm edit main.o kbd.o 在这个Makefile中有三个目标体(target),分别为edit、main.o和kbd.o,其中第一个目标体的依赖文件就是后两个目标体。
如果用户使用命令“make edit”,则make管理器就是找到edit目标体开始执行。
这时,make会自动检查相关文件的时间戳。
首先,在检查“main.o”、“kbd.o”和“edit”三个文件的时间戳之前,它会向下查找那些把“main.o”或“kbd.o”做为目标文件的时间戳。
比如,“main.o”的依赖文件为:“main.c”、“defs.h”。
如果这些文件中任何一个的时间戳比“main.o”新,则命令“gcc –Wall –O -g –c main.c -o main.o”将会执行,从而更新文件“main.o”。
在更新完“main.o”或“kbd.o”之后,make会检查最初的“main.o”、“kbd.o”和“edit”三个文件,只要文件“main.o”或“kbd.o”中的任比文件时间戳比“edit”新,则第二行命令就会被执行。
这样,make就完成了自动检查时间戳的工作,开始执行编译工作。
这也就是Make工作的基本流程。
接下来,为了进一步简化编辑和维护Makefile,make允许在Makefile中创建和使用变量。
变量是在Makefile中定义的名字,用来代替一个文本字符串,该文本字符串称为该变量的值。
在具体要求下,这些值可以代替目标体、依赖文件、命令以及makefile文件中其它部分。
在Makefile中的变量定义有两种方式:一种是递归展开方式,另一种是简单方式。
递归展开方式定义的变量是在引用在该变量时进行替换的,即如果该变量包含了对其他变量的应用,则在引用该变量时一次性将内嵌的变量全部展开,虽然这种类型的变量能够很好地完成用户的指令,但是它也有严重的缺点,如不能在变量后追加内容(因为语句:CFLAGS CFLAGS -O在变量扩展过程中可能导致无穷循环)。
为了避免上述问题,简单扩展型变量的值在定义处展开,并且只展开一次,因此它不包含任何对其它变量的引用,从而消除变量的嵌套引用。
递归展开方式的定义格式为:VARvar 简单扩展方式的定义格式为:VAR:var Make中的变量使用均使用格式为:VAR 变量名是不包括“:”、“”、“”结尾空格的任何字符串。
同时,变量名中包含字母、数字以及下划线以外的情况应尽量避免,因为它们可能在将来被赋予特别的含义。
变量名是大小写敏感的,例如变量名“foo”、“FOO”、和“Foo”代表不同的变量。
推荐在makefile内部使用小写字母作为变量名,预留大写字母作为控制隐含规则参数或用户重载命令选项参数的变量名。
下面给出了上例中用变量替换修改后的Makefile,这里用OBJS代替main.o和kbd.o,用CC代替Gcc,用CFLAGS代替“-Wall -O –g”。
这样在以后修改时,就可以只修改变量定义,而不需要修改下面的定义实体,从而大大简化了Makefile维护的工作量。
经变量替换后的Makefile如下所示: OBJS main.o kbd.o CC cc edit : OBJS CC OBJS -o edit main.o : main.c defs.h CC -c main.c kbd.o : kbd.c defs.h command.h CC -c kbd.c 可以看到,此处变量是以递归展开方式定义的。
Makefile中的变量分为用户自定义变量、预定义变量、自动变量及环境变量。
如上例中的OBJS就是用户自定义变量,自定义变量的值由用户自行设定,而预定义变量和自动变量为通常在Makefile都会出现的变量,其中部分有默认值,也就是常见的设定值,当然用户可以对其进行修改。
预定义变量包含了常见编译器、汇编器的名称及其编译选项。
下表2-2列出了Makefile中常见预定义变量及其部分默认值。
命 令 格 式 含 义 AR 库文件维护程序的名称,默认值为ar AS 汇编程序的名称,默认值为as CC C编译器的名称,默认值为cc CPP C预编译器的名称,默认值为CC –E CXX C编译器的名称,默认值为g FC FORTRAN编译器的名称,默认值为f77 RM 文件删除程序的名称,默认值为rm –f ARFLAGS 库文件维护程序的选项,无默认值 ASFLAGS 汇编程序的选项,无默认值 CFLAGS C编译器的选项,无默认值 CPPFLAGS C预编译的选项,无默认值 CXXFLAGS C编译器的选项,无默认值 FFLAGS FORTRAN编译器的选项,无默认值 表2-2 Makefile中常见预定义变量 可以看出,上例中的CC和CFLAGS是预定义变量,其中由于CC没有采用默认值,因此,需要把“CCGcc”明确列出来。
由于常见的Gcc编译语句中通常包含了目标文件和依赖文件,而这些文件在Makefile文件中目标体的一行已经有所体现,因此,为了进一步简化Makefile的编写,就引入了自动变量。
自动变量通常可以代表编译语句中出现目标文件和依赖文件等,并且具有本地含义(即下一语句中出现的相同变量代表的是下一语句的目标文件和依赖文件)。
下表2-3列出了Makefile中常见自动变量。
命 令 格 式 含 义 不包含扩展名的目标文件名称 所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件 lt 第一个依赖文件的名称 所有时间戳比目标文.