【Linux】 gcc/g++ 工具的使用及其动静态库的初步认识
前言我们写出来的 C/C 代码只是人比较容易理解的文本计算机并不能直接执行。想要把源代码变成一个可以运行的程序中间还要经过预处理、编译、汇编和链接而gcc与g就是 Linux 下经常用来完成这些工作的工具。本篇主要介绍一下gcc/g的基本使用、程序生成的完整过程以及链接阶段会接触到的动态库和静态库。1.gcc/g工具的简单介绍1.1gcc与g分别是什么gcc和g都属于 GCC 编译工具集。平时编译 C 语言程序时一般使用gcc编译 C 程序时一般使用g。二者完成编译时所使用的许多选项都是相同的不过 C 程序通常应该交给g处理因为g会按照 C 的方式编译并在链接时自动处理 C 标准库。刚开始使用时可以简单记成C 程序使用gccC 程序使用g。例如当前目录中有一个code.c文件#includestdio.h#defineM100intmain(){printf(hello Linux! %d \n,M);return0;}使用gcc可以直接把它编译成名为code的可执行文件gcc code.c-ocode其中-o用来指定输出文件的名字。如果不写-o codegcc在成功编译以后通常会生成一个名为a.out的文件。生成以后可以使用下面的方式运行./code对于 C 程序使用方式基本相同只需要把工具换成gg code.cpp-ocode1.2程序翻译的四个阶段平时使用的一条gcc命令看起来很简单但是它的背后其实连续完成了四个阶段。下面主要使用 C 程序进行演示g处理 C 程序时同样会经历这些过程下面把这四个阶段分别停下来观察一下。1.预处理预处理主要负责头文件展开、宏替换、条件编译以及去除注释等工作。使用-E选项可以让gcc完成预处理以后就停下来并把结果保存到code.i中gcc-Ecode.c-ocode.icode.i仍然是一个文本文件可以直接使用 Vim 打开。由于stdio.h中的内容被展开到了文件中所以它往往会比原来的源文件大很多源代码中的宏M也会被替换成对应的100。2.编译这里所说的编译是四个阶段中的第二步。gcc会检查代码是否存在语法等问题并把预处理后的 C 代码翻译成汇编代码。使用-S选项可以在生成汇编代码以后停下来gcc-Scode.i-ocode.s生成的code.s同样是文本文件不过里面已经变成了与当前平台相关的汇编指令。这里先知道 C 代码会被翻译成汇编即可不需要逐条研究这些指令。3.汇编汇编阶段会把code.s中的汇编指令转换成机器能够识别的二进制指令。使用-c选项可以让gcc生成目标文件以后停止不继续进行链接gcc-ccode.s-ocode.o生成的code.o叫做可重定位目标文件。它已经是二进制文件但是还要经过链接才能成为完整的可执行程序所以不能直接运行。4.链接一个程序中可能存在多个目标文件还可能调用printf这类由系统库提供的函数。链接阶段会把需要的目标文件和库联系起来最后生成可执行文件gcc code.o-ocode到这里一个源文件才真正变成了可以运行的程序。前面分步执行主要是为了观察中间过程平时直接使用gcc code.c -o code就会自动完成这四步。如果一个程序由多个源文件组成可以先让每个源文件分别生成自己的.o文件最后再统一链接gcc-ccode1.c-ocode1.o gcc-ccode2.c-ocode2.o gcc code1.o code2.o-oapp这样修改其中一个源文件时只需要重新编译受影响的部分再完成最后的链接即可。把几个阶段放在一起以后对应关系如下阶段常用选项生成文件文件中的主要内容预处理-Ecode.i展开头文件、完成宏替换后的C代码编译-Scode.s汇编代码汇编-ccode.o可重定位的二进制目标文件链接无专门的停止选项code最终可执行文件1.3几个常用的编译选项gcc与g的选项非常多这里只整理本篇用到的几个常见选项选项作用-o 文件名指定生成文件的名字-E只完成预处理-S生成汇编代码以后停止-c生成目标文件以后停止不进行链接-D宏值在命令行中定义宏-static生成程序时采用静态链接例如如果源文件中使用了宏M但是没有在代码中写#define M 100也可以在编译时通过-D进行定义gcc code.c-ocode-DM100命令行中定义的宏同样会在预处理阶段参与替换。这种方式可以在不修改源文件的情况下为不同的编译过程传入不同的宏。2.链接与库文件的简单认识2.1为什么程序需要库这里可能会有一个疑问前面的程序调用了printf函数但是我们并没有自己实现这个函数它最后为什么还能正常运行呢stdio.h头文件中主要提供的是printf等函数的声明让编译器知道这个函数应该怎样调用真正已经编译好的函数实现则保存在 C 标准库中。程序进行链接时会找到对应的库以及其中的函数实现把前面留下的调用关系处理好。库可以理解为别人已经写好并编译好的常用代码集合。很多程序都需要字符串处理、输入输出、数学运算等功能如果每个程序都重新实现一遍不仅麻烦也会产生大量重复代码。把它们整理成库以后需要的时候直接使用即可。2.2Linux中的库文件Linux 中常见的库主要有两种动态库的后缀一般是.so静态库的后缀一般是.a。库文件通常还会采用lib库名.后缀的命名方式例如 C 标准动态库libc.so.6中lib是库文件常用的前缀c表示库名.so表示它是动态库最后的数字则与库版本有关。Windows 中还会见到.dll和.lib不过.lib不一定就是静态库这里不继续展开不要和 Linux 下的后缀混淆。想查看一个程序依赖了哪些动态库可以使用ldd命令ldd code从结果中可以看到当前程序依赖了libc.so.6。后面显示的是这个库在当前系统中实际找到的位置。不同发行版、系统架构和安装环境中的路径可能不同所以查看时应该以自己机器上的结果为准。如果查看的是g生成的 C 程序依赖列表中通常还会出现 C 标准动态库libstdc.so.6。需要注意ldd主要用来查看动态依赖并不能用它判断源码中包含了哪些头文件。头文件参与编译库文件参与链接和运行它们完成的是两件不同的事情。3.动态库与静态库动态库和静态库都可以向程序提供已经编译好的代码最主要的区别在于链接以后这些库代码最终放在哪里。3.1动态链接在没有添加其他特殊选项时gcc生成的程序通常采用动态链接。动态链接不会在生成可执行文件时把动态库中的代码全部复制进去而是记录程序依赖了哪些共享库。程序准备运行时系统会找到这些动态库并加载到内存中供程序使用。gcc code.o-ocode可以使用file命令查看文件类型filecode输出中的dynamically linked说明这个可执行文件采用了动态链接。动态库可以被多个程序共同使用程序文件通常比较小库修复或更新时也不一定需要重新编译所有程序。不过程序运行时必须能够找到版本兼容的动态库否则就可能出现动态库缺失或版本不匹配的问题。3.2静态链接静态链接会在链接阶段从静态库中取出程序需要的代码并把它们加入最终的可执行文件中。使用-static选项可以尝试生成静态链接的程序gcc code.o-ocode-static-static因为需要的库代码已经进入了code-static所以这个文件通常会比动态链接生成的code大很多。运行时也不再需要原来的静态库文件可以使用下面的命令进行比较ls-lhcode code-static ldd code-static对于静态链接的程序ldd通常会提示它不是动态可执行文件。静态链接便于得到一个对共享库运行时依赖较少的程序不过库中的代码如果发生变化一般需要重新链接才能进入新的可执行文件。有些精简的云服务器默认并没有安装 C/C 静态库这时使用-static可能会因为找不到静态库而失败。C 程序进行静态链接通常需要glibc-staticC 程序还需要libstdc-static。在 CentOS 环境中有管理权限时可以根据需要安装sudoyuminstall-yglibc-static libstdc-static3.3二者的简单比较对比内容静态链接动态链接库代码的位置链接时加入可执行文件可执行文件记录依赖运行前由装载器加载动态库可执行文件大小通常比较大通常比较小运行时对原库的依赖不再需要原来的.a文件需要系统中存在版本兼容的.so文件代码复用不同程序通常各自带有一份所需库代码多个程序可以共同使用系统中的同一份动态库库更新以后一般需要重新链接程序保持兼容时可以直接使用更新后的动态库刚开始接触gcc/g时先能够分清code.i、code.s、code.o和最终可执行文件分别处于哪个阶段再记住 Linux 下.so是动态库、.a是静态库最后会用file和ldd观察链接结果就可以了。至于动静态库怎样制作和发布后面真正用到时再继续补充。完