2.1.GCC编译器
linux系统下主要的编译器为GCC(GNU Complier Collection),最初GCC只是一个C语言编译器(GNU C Complier),现在GCC已经是一个包含众多语言的编译器了,也就是GNU编译器套件的意思。
GCC能将C、C++语言源程序和目标程序编译链接为可执行文件,如果没有给出可执行文件的名字,GCC将生成名为“a.out”文件。
2.1.1 基本工作流程
使用GCC将C语言源程序生成可执行文件的过程需要经过四个相互关联的步骤:预处理,编译,汇编,链接。
- 调用“cpp”进行预处理生成.i文件。处理所有”#include”预编译指令,#if #endif条件预编译指令,“#define”宏定义,删除注释。
命令:gcc -E -o hello.i hello.c
- 调用“ccl”进行编译。生成.s汇编代码文件。
命令:gcc -S -o hello.s hello.i
- 调用“as”指令进行汇编,则是一个针对汇编语言的步骤。生成机器唯一识别的.o二进制目标文件。
命令:gcc -c -o hello.o hello.s
- 调用“ld”完成最后的链接工作,将汇编形成的obj文件,系统库的obj文件和库文件链接起来,形成可执行文件。
2.2.2 GCC编译器基本用法
1.生成带有调试信息的可执行文件(-g)
gcc -g -o outfile infile
此时将在编译时加上调试信息,以便供GDB调试器调试。
2. 设置自定义头文件路径(-I dirname)
gcc -I dirname infile
将dirname中所指的目录加入程序头文件目录列表中,也就是在系统预设包含文件目录之前先到dirname目录下搜寻相应的头文件。
第一种情况,预处理程序cpp在系统预设包含文件目录(如/usr/include)中搜寻相应的文件。
第二种情况,cpp先在当前目录中搜寻头文件,然后再去系统预设包含文件目录(如/usr/include)中搜寻。
3.编译时加载库文件(-l name)
2.2 Makefile文件的使用
一个工程有很多函数,但只有一个main函数,其他函数可能被写在工程的不同源文件中。
可采用两种方式编译生成可执行文件。
所以用户可以在一个名为Makefile文件中定义一系列的规则来指定需要编译的文件,如哪些文件需要先编译,那些
文件需要后编译,那些文件需要重新编译。这样用户只需输入make命令,即可根据makefile文件中的规则变异工程文件。
对于大型的开发项目来说,可能包括很多源文件,使用make和makefile文件可以清晰地理顺各个源文件之间的关系。
2.3.1 make命令
make主要是通过make命令调用makefile文件,通过makefile文件描述源程序之间的相互依赖关系,并自动维护编译工作。
make调用格式:
- “make 目标”,查找makefile文件中指定的目标
一般都省略目标直接用make来查找makefile文件中的第一个目标。
2.3.2 makefile文件的命名
通常应该使用“makefile”或“Makefile”作为一个makefile文件的文件名。
用户只需要输入make命令即可根据当前目录下的makefile文件中的规则编译工程文件。2.3.3 makefile文件的内容
1.显式规则
描述了如何更新一个或多个目标文件。
如果不在vim中敲tab,gcc将会报Makefile missing separator. Stop.错误。如果hello不存在则进行编译。
- 如果hello日期没有hello.c日期新则进行编译。
2.隐含规则
隐含规则不需要在makefile命令中明确给出重建特定目标文件所需要的细节描述,而是make命令根据一类目标文件(通常根据文件名的后缀)自动推导。
同时,MakeFile文件还支持一些预设的自动化变量,且只能出现在规则的命令中。
$ ^——所有的依赖文件,以空格隔开
$<——第一个依赖文件的名称
$@——目标的完整名称。3.变量定义
类似于C语言的宏,大小写敏感(推荐大小写搭配名字)
Makefile文件仅用一个字符或者字符串作为变量来代表一段字符串。
定义之后就可以引用该变量。
引用方式为:$(变量名)或 ${变量名}
4.指示符
5.注释
通过#进行注释。
clean规则用于删除中间生成的临时文件和最终的可执行文件“-f”选项强制执行。2.3 GDB调试器
编写源程序后通过编译器可以生成可执行程序。错误可以分为编译错误和执行错误。
编译错误是指在gcc编译时产生错误,需要根据错误提示找到错误,从第一个错误开始找;然后在网上搜索错误提示;最后总结编译错误的解决方法。
运行错误是指在gcc时正确,但在运行时产生了错误。可以通过GDB调试和打印调试找出问题。
GDB是一种字符界面调试器。
打印调试是指在程序适当位置加printf提示语句(记得在printf中加\n),在调用函数后根据函数返回值结合error值查找错误。2.3.1 GDB使用
使用gcc的-g选项编译源程序,生成带调试信息的可执行文件。
在命令行中输入:
gdb hello
进入GDB调试环境:
2.3.2 常用命令
break:设置断点。
clear:删除刚才停止处的断点。
continue:从断点开始继续执行
kill:异常中止在gdb下控制的程序
print:显示变量或表达式的值
next:执行下一个源程序行,若断点所在行为函数调用,则不进入函数内部
quit:退出gdb
run:执行该程序
step:执行下一个程序行,若断点所在行为函数调用,则进入函数内部。
whatis:显示变量或函数类型
上述指令可直接缩写为首字母。
2.4 库
库函数是由系统提供的,供程序员开发时调用,完成特定功能的函数,如printf等,一般是.o目标代码,都需要将.c源文件生成目标文件。
库函数便于编程;可以隐藏细节,降低开发难度和开发周期;也可以保护商业软件的知识产权。
存放函数的文件就像存放函数的仓库,如Glibc库,提供了系统调用和C库的基本函数。
linux库有两种形式:静态库和动态库(共享库)。由.o文件创建。
Linux的库都放在/usr/lib和/lib目录中。GCC在链接时将首先搜索这两个目录。linux将首先搜索指定库的共享库,如果找不到,才会去搜索静态库。
静态库的代码在编译时就已经链接到开发人员开发的应用程序中;
动态库(共享库)只是在程序开始运行时载入,在编译时,只需要简单的指定需要使用的库函数。
因为共享库并没有在程序里包括库函数的内容,只是包含了对库函数的引用,因此可执行文件的代码规模较小。
库文件名都是由前缀lib,库名以及后缀组成。
2.4.1 静态库
静态库的创建与使用:
将fun1.c和fun2.c生成静态库libc.a
- 分别生成目标文件fun1.o,fun.o
gcc -c fun1.c fun2.c
- 生成静态库(libxxx.a)
ar -rc/rsc libc.a fun1.o fun2.o
- 在usehello.c中使用静态库libc.a
gcc -o usehello_static usehello.c -L -lc -static
-static是强制调用libc.a静态库,如果共享库和静态库同时存在的话,优先链接共享库。
如果将libc.a复制到/lib或/usr/lib,就可以去除上述命令中的-L命令。
cp libc.a /usr/lib
可使用lld命令或ls -l命令查看usehello_static依赖库
lld usehello_static
ls -l usehello_static
2.4.2 动态库
so是 share object的缩写。
动态库的使用与创建:
- 生成目标文件fun1.o fun2.o:
gcc -fpic -c fun1.c fun2.c
-fpic产生位置独立的代码,pic是position independence code的缩写。因为在编译时,还不知道装入内存的位置,所以不加上此选项,库文件不会正确执行。
- 生成共享库
gcc -shared -o libc.so fun1.o fun2.o - 共享库的使用
共享库默认搜索路径是/usr/lib或/lib,拷贝过去:
cp libhello.so /usr/lib
使用动态链接库:
gcc -o usehello_dynamic usehello.c –lc
或者不拷贝直接使用以下命令编译:
gcc -o usehello_dynamic usehello.c –L ./ –lc