天天看点

《嵌入式 Linux C 语言应用程序设计(修订版)》——2.3 嵌入式Linux编译器GCC的使用ifndef HELLO_H define HELLO_H endifinclude include include "hello.h"

本节书摘来异步社区《嵌入式 linux c 语言应用程序设计(修订版)》一书中的第2章,第2.3节,作者:华清远见嵌入式培训中心,孙琼,更多章节内容可以访问云栖社区“异步社区”公众号查看

嵌入式 linux c 语言应用程序设计(修订版)

作为自由软件的旗舰项目,richard stallman在十多年前刚开始写作gcc的时候,还只是仅仅把它当作一个c程序语言的编译器,gcc的意思也只是gnu c compiler而已。

经过了这么多年的发展,gcc已经不仅仅能支持c语言,它现在还支持ada语言、c++语言、java语言、objective c语言、pascal语言、cobol语言,并支持函数式编程和逻辑编程的mercury语言等。而gcc也不再单只gnu c语言编译器的意思了,而是变成了gnu编译器家族了。

正如前文中所述,gcc的编译流程分为了4个步骤,分别为。

预处理(pre-processing)。

编译(compiling)。

汇编(assembling)。

链接(linking)。

编译器通过程序的扩展名可分辨编写原始程序码所用的语言,由于不同的程序所需要执行编译的步骤是不同的,因此gcc根据不同的后缀名对它们进行分别处理,表2.6指出了不同后缀名的处理方式。

《嵌入式 Linux C 语言应用程序设计(修订版)》——2.3 嵌入式Linux编译器GCC的使用ifndef HELLO_H define HELLO_H endifinclude include include "hello.h"

gcc使用的基本语法为:

/hello.h/

typedef unsigned long val32_t;

/hello.c/

int main()

{

}<code>`</code>

1.预处理阶段

gcc的选项“-e”可以使编译器在预处理结束时就停止编译,选项“-o”是指定gcc输出的结果,其命令格式为如下所示。

[root@localhost gcc]# gcc –e –o hello.i hello.c<code>`</code>

在此处,选项‘-o’是指目标文件,由2.6表可知,‘.i’文件为已经过预处理的c原始程序。以下列出了hello.i文件的部分内容。

[root@localhost gcc]# gcc –s –o hello.s hello.i<code>`</code>

以下列出了hello.s的内容,可见gcc已经将其转化为汇编了,感兴趣的读者可以分析一下这一行简单的c语言小程序用汇编代码是如何实现的。

[root@localhost gcc]# gcc –c hello.s –o hello.o<code>`</code>

4.链接阶段

在成功编译之后,就进入了链接阶段。在这里涉及一个重要的概念:函数库。

在这个程序中并没有定义“printf”的函数实现,在预编译中包含进的“stdio.h”中也只有该函数的声明,而没有定义函数的实现,那么,是在哪里实现“printf”函数的呢?

最后的答案是:系统把这些函数实现都已经被放入名为libc.so.6的库文件中去了,在没有特别指定时,gcc会到系统默认的搜索路径“/usr/lib”下进行查找,也就是链接到libc.so.6库函数中去,这样就能实现函数“printf”了,而这也就是链接的作用。

完成了链接之后,gcc就可以生成可执行文件,其命令如下所示。

[root@localhost gcc]# ./hello

hello, embedded world 5<code>`</code>

本节主要讲解gcc的警告提示功能。gcc包含完整的出错检查和警告提示功能,它们可以帮助linux程序员写出更加专业和优美的代码。

读者千万不能小瞧这些警告信息,在很多情况下,含有警告信息的代码往往会有意想不到的运行结果。

首先读者可以先看一下以下这段代码:

[root@ft charpter2]# gcc -wall wrong.c -o wrong

wrong.c:4: warning: return type of 'main' is not 'int'

wrong.c: in function 'main':

wrong.c:5: warning: unused variable 'tmp'<code>`</code>

可以看出,使用‘-wall’选项找出了未使用的变量tmp以及返回值的问题,但没有找出无效数据类型的错误。

2.非wall类警告提示

非wall类的警告提示中最为常用的有以下两种:“-ansi”和“-pedantic”。

(1)“-ansi”

该选项强制gcc生成标准语法所要求的告警信息,尽管这还并不能保证所有没有警告的程序都是符合ansi c标准的。使用该选项的运行结果如下所示:

[root@ft charpter2]# gcc -pedantic wrong.c -o wrong

wrong.c:5: warning: iso c90 does not support 'long long'

wrong.c:4: warning: return type of 'main' is not 'int'<code>`</code>

可以看出,使用该选项查看出了“long long”这个无效数据类型的错误。

1.linux函数库介绍

函数库可以看做是事先编写的函数集合,它可以与主函数分离,从而增加程序开发的复用性。linux中函数库可以有3种使用的形式:静态、共享和动态。

静态库的代码在编译时就已连接到开发人员开发的应用程序中,而共享库只是在程序开始运行时才载入。

动态库也是在程序运行时载入,但与共享库不同的是,动态库使用的库函数不是在程序运行使开始载入,而是在程序中的语句需要使用该函数时才载入。动态库可以在程序运行期间释放动态库所占用的内存,腾出空间供其他程序使用。

由于共享库和动态库并没有在程序中包括库函数的内容,只是包含了对库函数的引用,因此代码的规模比较小。

系统中可用的库都存放在/usr/lib和/lib目录中。库文件名由前缀lib和库名以及后缀组成。根据库的类型不同,后缀名也不一样。

stnd001注意 共享库和动态库的后缀名由.so和版本号组成。

静态库的后缀名为.a。

如:数学共享库的库名为libm.so.5,这里的标识字符为m,版本号为5,libm.a则是静态数学库。在linux系统中系统所用的库都存放在/usr/lib和/lib目录中。

2.相关路径选项

由于库文件的通常路径不是在系统默认的路径下,因此,首先要使用调用路径选项来指定相关的库文件位置,这里首先讲解两个常用选项的使用方法。

(1)“-i dir”

在gcc中使用头文件在默认情况下是在主程序中所设定的路径,那么如果想要改变该路径,用户则可以使用“-i”选项。“-i dir”选项可以在头文件的搜索路径列表中添加dir目录。这时,gcc就会到相应的位置查找对应的目录。

比如在“/root/workplace/gcc”下有两个文件:

[root@localhost gcc] gcc hello.c –i /root/workplace/gcc/ -o hello<code>`</code>

这样,gcc就能够执行出正确结果。

0115小技巧 在include语句中,“&lt;&gt;”表示在标准路径中搜索头文件,在linux中默认为“/usr/include”。故在上例中,可把hello1.c的“#include”改为“#include "my.h"”,这样就不需要加上“-i”选项了。

(2)“-l dir”

选项“-l dir”的功能与“-i dir”类似,其区别就在于“-l”选项是用于指明库文件的路径。例如有程序hello_sq.c需要用到目录“/root/workplace/gcc/lib”下的一个动态库libsunq.so,则只需键入如下命令即可。

[root@localhost gcc] gcc -o dynamic -l /root/lq/testc/lib/dynamic.o -lmydynamic<code>`</code>

那么,若系统中同时存在文件名相同的静态库文件和动态库文件时,该链接选项究竟会调用静态库文件还是动态库文件呢?

经测试后可以发现,系统调用的是动态库文件,这是由于linux系统中默认的是采用动态链接的方式。这样,若用户要调用含有同名动态库文件的静态库文件,则在“-l”后需要显示地写出包含后缀名的文件名,如:要调用libm.a库文件时就需写作“-llibm.a”。

gcc可以对代码进行优化,它通过编译选项-on来控制优化代码的生成,其中n是一个代表优化级别的整数。对于不同版本的gcc来讲,n的取值范围及其对应的优化效果可能并不完全相同,比较典型的范围是从0变化到2或3。

不同的优化级别对应不同的优化处理工作,如使用优化选项-o主要进行线程跳转(thread jump)和延迟退栈(deferred stack pops)两种优化。

使用优化选项-o2除了完成所有-o1级别的优化之外,同时还要进行一些额外的调整工作,如处理器指令调度等;选项-o3则还包括循环展开和其他一些与处理器特性相关的优化工作。

虽然优化选项可以加速代码的运行速度,但对于调试而言将是一个很大的挑战。因为代码在经过优化之后,原先在源程序中声明和使用的变量很可能不再使用,控制流也可能会突然跳转到意外的地方,循环语句也有可能因为循环展开而变得到处都有,所有这些都将使调试工作异常坚难。

建议在调试的时候最好不使用任何优化选项,只有当程序在最终发行的时候才考虑对其进行优化。

继续阅读