GNU工具链的使用(一)

2019-07-13 05:23发布

        在我们写好C或者C++源代码后我们需要使用编译工具对我们的工程进行编译连接生成一个可以再特定平台下运行的可执行程序。由于GNU软件都遵循GPL(公共许可证)并且对我们来说是开源免费的,所以在linux中我们经常使用GNU工具对源代码进行编译连接即使在arm平台下进行嵌入式linux开发也不例外。         我们现在来回顾一下C/C++语言的编译连接过程,如下图所示:


        我们在linux下一般使用vim软件进行源程序的编辑(还有geany codeblocks等),让后通过gcc直接将程序编译成目标二进制文件(.o),最后使用ld工具将目标二进制文件与库连接生成完成的程序。由于在PC环境下所有的软件都做得很完善(gnu工具链也不例外),链接指令ld(将各个目标二进制文件通过对应关系和连接脚本连接成可执行的程序)我们是用不到的,我们将讲述如何在linux操作系统中编译程序以及使用如何调试,这里涉及到的工具有:gcc,gdb makefile,我们首先讲述gcc的基本使用方法,并在讲述完gcc后补充一下如何生成自己的库文件。

GCC命令格式

        每一个命令都有它的格式要求(GCC也不例外),但是GCC的命令格式还是比较简单的,如下所示: gcc [选项 | 文件名]
        其中,选项部分也就是我们给GCC指定的参数部分,告诉编译器我们的程序如何编译,编译后的程序叫什么名字等等,文件名部分用于告诉编译器要对哪些文件进行编译具体例子如下: gcc hello.c gcc -o foo foo.c        第一行表示通过gcc编译hello.c生成一个可执行文件,如果没有指定程序的名字,默认程序的名字是a.out,也就是第一行命令会生成a.out的可执行程序。对于第二行使用-o(output_filename)参数,表示我们要编译foo.c文件且指定名字是foo.


GCC常用参数

        下面给出GCC常用的参数,只要有个大概了解即可,如果碰到了可以在这里快速查阅: -g :给生成的程序添加调试信息,无该选项则gdb无法正常调试程序 -o oput_filename :指定程序名字,不加该参数程序默认名为a.out -c :对C/C++源代码进行编译不连接(生成目标文件.o) -wall :gcc尽可能多的给出编译信息 -std=val :选择C语言标准,可选的有c90(c89)、c99(c9x)、c1x(2011年C标准) -I :添加头文件搜索路径(-Idir),dir就是我们添加的路径 -L :添加库文件的搜索路径 -l :指定使用哪个库,例如-lm表示libm.a这个库文件,gcc库命名规则是lib+库作用+.a,-lm是libm.a的缩写。 -E :只对C/C++源代码进行预处理(预处理),不进行编译和连接 -S :表示文件在编译阶段生成汇编文件后(二进制目标文件后)停止编译 -pedantic :对于不兼容标准C/C++语法的地方给予警告提示 -werror:通知编译器一切警告(warning)都是都是错误(error),让编译器停止工作迫使程序员修改代码。

1.编译一个简单的程序

        相信同学们对于我们刚学C语言时候写的hello.c程序并无陌生,hello.c中就包含一个main函数和一个printf输出语句,用于输出helloworld,那么我们在linux操作系统下如何编译这个hello.c文件呢?其实也是很简单的:     gcc -o helloworld hello.c gcc -o game screen.c game.c input.c         我们使用gcc命令编译C源代码,-o表示指定输出文件名也就是我们程序编译后的文件名,我们可以指定多个源文件参与程序的编译。

2.GCC编译程序的步骤

         一般来说编译一个程序的步骤是,将源程序编译生成汇编文件,然后将汇编文件编译成目标二进制代码(.o文件),最后将各个目标二进制代码与库连接(例如我们使用到stdio.h输入输出语句,就要用到库的这部分代码)生成可执行的程序,之前我已经说过PC机上的软件功能都很智能,我们可以使用"gcc -o hello hello.c"让编译器自动的帮我们完成上述步骤直接生成程序,我们也可以按照上述步骤生成我们的程序: gcc -o hello.i -E hello.c C语言的预编译过程,同学们预编译hello.c,用vim hello.i看看下 gcc -o hello.s -S hello.i 将预处理后的源程序生成汇编文件,用vim看下helo.s gcc -o test test.s 将hell.s通过汇编程序生成目标代码(内部自动调用ld工具与特定库连接生成可执行程序)
        这里我要说明一下:第一行我们可以写成"gcc -E helo.c  -o hello.i",gcc没有对各个参数之间的顺序有太多要求,另外我们之前的"gcc -o hello hello.c"也可以写成”gcc hello.c  -o hello“,但是我还是建议遵循gcc的规范:gcc [选项 | 文件名] 的格式,这样的话会使得我们写的命令的可读性会更好。

3.添加头文件的搜索路径

        一般来说我们代码有.c和.h两种文件,其中.c文件参与程序的编译,因为.c文件包含变量定义和执行代码,而.h则是用于声明函数或者变量,gcc根据它来检测有没有使用没有定义的变量或者函数,也就是用于检查的错误。如果我们使用的头文件不在GCC的标准目录下(存stdio.h头文件的目录),我们则需要告诉gcc我们的头文件在哪个目录以方便找到我们的头文件: gcc -o oenpgl -I/user/opengl/include opgl.c        例如我们的opgl.c使用opgl.h,但是这个头文件在/user/opengl/include目录下我们则需要使用-I(I for include)参数指定我们头文件的目录,对于写过shell或者常敲命令的人来说,”.“应该不陌生,例如"cd ..”表示跳到上层目录而"."表示当前目录,例如执行当前目录下的hello程序可以写成”./hello“,对于gcc也不例外,如果我们的头文件在当前目录下,可以这么指定: gcc -o hello -I. hello.c
    如果我们hello.c使用hello.h,gcc在标准目录中找不到时就会在当前目录里面寻找使用“-I.”的本质就是表示把当前目录添加到gcc的头文件搜索目录中。


4.指定使用到的库

    一般来说gcc不会把所有库都连接而只会默认连接某些常用的库,当我们需要连接不常用的库,这时我们需要告诉gcc需要使用哪些库,编译器就会自动帮我们把它给连接上,例如我们写了一个多线程的程序需要使用libpthread.a库,我们可以使用-lpthread来指明要连接这个库,我再次重申gcc库的命名规则:”lib+库名称.lib“,-l(l for library)-lpthread是libpthread.a的缩写(这是gcc的规矩我们只能遵守),例如: gcc -lpthread test.c -o test.c 再次重申我们gcc对各个不分的参数没有绝对要求,按照个人喜好摆放即可

5.添加搜索库的路径

        与头文件(.h)类似,如果我们使用的不是gcc标准库,此时gcc是不知道我们使用的库的路径,这时需要将库所在的目录告知gcc(添加到gcc的搜索库路径中),这样我们就可以通过-l参数使用我们的库,一般来说-l和-L是配套使用,-L用于添加库的搜索目录,-l则表示使用哪个库,如果用-l参数的话,要注意库名的命名要遵循gcc规范。例如: gcc -o apple apple.c /home/lib/libapple.a gcc -o apple -L/home/lib apple.c -lapple         第一行用于说明不用“-L”和“-l”的情况下如何添加库,但是这样写一般不好。因此我们推荐第二种方法(第二行)。我们通过-L说明我们的库的目录在/home/lib下,然后使---lapple指定libapple.a库,习惯这样的表达的话会方便程序员记忆和理解。 


新建一个库

两种库类型

         我们可以自己定义一个新的库,库的本质就是给程序员导出函数的,我会先讲解使用哪些命令进行创建库,然后在用一个简单的实例说明如何创建一个库。          首先我们要知道库有两种,一种是静态库(.a),而另一种是则是共享库(.so),所谓的静态库就是在编译时会把这个库编译进我们的程序里,这会使得我们的程序变得很大,但是这也有一个好处,当我们的程序在别的系统运行时,程序不会因系统缺少对应的库而无法运行。对于共享库来说,程序编译时不会把共享库编译进程序里。如果我们程序调用printf函数时,系统发现printf函数对应的库没有被加入到内存时会自动的把共享库调入内存。这样做可以很好的节约内存资源(动态库在内存只有一份,而使用静态库编译出来程序每次运行程序都会把程序调入内存当然也包括静态库),但是如果这个程序在别的系统运行而恰好系统没有这个共享库时,程序是跑不了的。       我们要使用ar命令构造出我们的静态库,然后用ranlib命令将我们构造出来的静态库具有的函数生成一个内容表,这个表是个连接命令(ld)用的。       在《linux程序设计》第四版中给出了建静态库的例子: ar crv libfoo.a bill.o fred.o randlib libfoo.a       通过ar命令生成一个静态库libfoo.a然后把bill.o和fred.o目标文件添加进库里,最后通过randlib生成libfoo.a的函数内容表。其中ar指令中的crv分别表示的是,c (create)创建一个一个归档文件(什么是归档文件?术语而已就是创建一个文件-o.0),r(replace)把.o的目标文件插入到归档文件中,如果这个.o文件已经存在于库中我们用当前的.o文件代替库中的对应部分,v(verbose)在生成库时候在控制台中输出详细信息。

建立一个库的流程

        首先我们创建lib.c: #include int my_hello(const char* str) { printf("in my lib: %s",str); return 0; }
        然后建立一个hello.c: extern int my _hello(const char* str); int main(int argc, char *argv) { my_hello("do you see me ? "); return 0; }              构造库与编译程序 gcc -o libmy.o -c lib.c ar crv libmy.a libmy.o gcc -o t -L. hello.c -lmy 貌似-lmy要放在后面不然会编译报错 - -
        执行程序 ./t           效果图如下: