GCC 编译器使用详解:从编译过程到动态库静态库制作
GCC 编译器使用详解从编译过程到动态库静态库制作本文详细讲解 GCC 编译器的使用方法包括 C 程序的编译过程、常用编译选项、多文件编译、动态库与静态库的制作与使用以及一些非常实用但容易忽略的选项。无论你是刚入门的嵌入式开发者还是想深入理解编译原理的爱好者这篇文章都能帮你理清思路。文章目录GCC 编译器使用详解从编译过程到动态库静态库制作1. 从一个简单的 hello.c 说起1.1 C 文件与头文件的作用1.2 头文件的搜索路径怎么确定交叉编译器中头文件的默认路径怎么自己指定头文件目录1.3 库文件的搜索路径与链接怎么确定交叉编译器中库文件的默认路径怎么自己指定库文件目录和要用的库2. GCC 编译过程四步详解在这里插入图片描述2.1 预处理Preprocessing2.2 编译Compilation2.3 汇编Assembly2.4 链接Linking2.5 一条命令完成所有步骤3. GCC 常用编译选项速查表4. 多文件编译4.1 一起编译、链接4.2 分开编译最后统一链接5. 动态库共享库的制作与使用5.1 制作动态库的步骤5.2 使用动态库编译程序5.3 运行依赖动态库的程序6. 静态库的制作与使用6.1 制作静态库6.2 使用静态库编译程序6.3 运行7. 很有用的 GCC 选项你可能不知道但很实用7.1 查看预处理结果宏展开、头文件路径7.2 生成依赖文件用于 Makefile7.3 快速查看编译器的默认头文件路径和库路径8. 交叉编译特别说明8.1 交叉编译时的注意事项8.2 交叉编译动态库的特殊要求9. 总结与速查1. 从一个简单的 hello.c 说起1.1 C 文件与头文件的作用先来看一个最简单的 C 程序hello.cc#include stdio.h /* 执行命令./hello weidongshan * argc 2 * argv[0] ./hello * argv[1] weidongshan */ int main(int argc, char **argv) { if (argc 2) printf(Hello, %s!\n, argv[1]); else printf(Hello, world!\n); return 0; }C 文件.c存放函数实现和变量定义。比如上面定义了main函数。头文件.h存放声明函数原型、宏定义、类型定义等。#include stdio.h就是把标准输入输出库的声明包含进来这样编译器才知道printf是什么。通俗理解头文件相当于“菜单”告诉你有哪些函数可用C 文件相当于“厨房”里面是函数的具体做法。#include就是你把菜单拿过来看一眼。1.2 头文件的搜索路径当你在代码中写#include stdio.h或#include myheader.h时编译器需要找到这个头文件的实际位置。写法搜索顺序#include stdio.h首先在系统默认的头文件目录中查找如/usr/include#include myheader.h首先在当前目录查找如果找不到再去系统目录查找怎么确定交叉编译器中头文件的默认路径以交叉编译工具链arm-buildroot-linux-gnueabihf-gcc为例你可以进入工具链的安装目录执行bashfind -name stdio.h通常会输出类似text./arm-buildroot-linux-gnueabihf/sysroot/usr/include/stdio.h其中sysroot/usr/include就是这个交叉编译器的默认头文件目录。怎么自己指定头文件目录使用-I选项大写字母 Ibashgcc -I /my/custom/include -o hello hello.c这样编译器就会优先去/my/custom/include目录下找头文件。1.3 库文件的搜索路径与链接库文件.a静态库或.so动态库是已经编译好的函数集合。比如printf的实现就在libc.so或libc.a中。怎么确定交叉编译器中库文件的默认路径在工具链目录下执行bashfind -name lib通常会得到多个lib目录比如arm-buildroot-linux-gnueabihf/libarm-buildroot-linux-gnueabihf/usr/lib进去看看如果里面有很多.so文件那就是库文件路径。怎么自己指定库文件目录和要用的库需求选项示例指定库文件搜索目录-L-L /my/lib/path指定要链接的库去掉前缀lib和后缀.so/.a-l-lsub表示链接libsub.so或libsub.a示例假设你的程序需要链接libabc.so它位于/opt/libs目录下bashgcc -o myapp main.c -L/opt/libs -labc2. GCC 编译过程四步详解在这里插入图片描述一个.c文件变成可执行文件需要经过预处理 → 编译 → 汇编 → 链接四个步骤。texthello.c → [预处理] → hello.i → [编译] → hello.s → [汇编] → hello.o → [链接] → hello日常用语我们经常把整个流程统称为“编译”但严格来说编译只是其中第二步。2.1 预处理Preprocessing任务处理所有以#开头的指令包括展开宏定义#define插入头文件#include处理条件编译#if、#ifdef输出文件.i文件文本格式仍然是 C 代码命令bashgcc -E -o hello.i hello.c-E表示只进行预处理不编译、不汇编、不链接。 你可以打开hello.i看看原来几行的hello.c会变成几百甚至几千行因为stdio.h的内容被完整插入了。2.2 编译Compilation任务将预处理后的.i文件翻译成汇编代码。输出文件.s文件汇编语言命令bashgcc -S -o hello.s hello.i-S表示只进行预处理编译生成汇编代码后停止。 实际上GCC 内部调用了一个名为cc1的程序来完成预处理和编译两步。你可以加上-v选项看到它。2.3 汇编Assembly任务将汇编代码.s文件翻译成机器指令生成目标文件Object File。输出文件.o文件二进制但尚未链接命令bashgcc -c -o hello.o hello.s-c表示只进行预处理、编译、汇编不链接。 汇编这一步使用的工具是as。2.4 链接Linking任务将多个.o目标文件与系统库如libc链接在一起解决符号引用比如printf的实际地址生成最终的可执行文件。输出文件可执行文件如hello命令bashgcc -o hello hello.o如果还有其他.o文件一并列出bashgcc -o program main.o sub.o utils.o 链接器通常是ld或collect2GCC 的一个封装。2.5 一条命令完成所有步骤bashgcc -o hello hello.c这条命令等价于上面四步的串联。日常开发中我们一般就用这一条。3. GCC 常用编译选项速查表选项作用示例-E只做预处理不编译、汇编、链接gcc -E -o hello.i hello.c-S只做预处理编译生成汇编代码gcc -S -o hello.s hello.c-c只做预处理编译汇编生成目标文件.ogcc -c -o hello.o hello.c-o指定输出文件名gcc -o myapp hello.c-I添加头文件搜索目录gcc -I ./include -o app main.c-L添加库文件搜索目录gcc -L ./lib -o app main.c -lmylib-l链接指定的库去掉 lib 前缀和后缀gcc -o app main.c -lm链接数学库 libm.so-static静态链接生成的可执行文件不依赖动态库gcc -o hello hello.c -static-shared生成动态库共享库gcc -shared -o libsub.so sub.o-fPIC生成位置无关代码制作动态库时必需gcc -c -fPIC -o sub.o sub.c-v显示详细的编译过程查看内部调用了哪些工具gcc -v -o hello hello.c注意-l选项的库名省略了lib前缀和.so/.a后缀。例如-lsub会查找libsub.so或libsub.a。4. 多文件编译假设你有两个源文件main.c和sub.c。4.1 一起编译、链接bashgcc -o test main.c sub.c这条命令会一次性编译两个文件并链接成test。4.2 分开编译最后统一链接bash# 分别编译成目标文件 gcc -c -o main.o main.c gcc -c -o sub.o sub.c # 链接所有目标文件 gcc -o test main.o sub.o优点当只修改了一个文件时只需重新编译那个文件再链接即可节省大型项目的编译时间。5. 动态库共享库的制作与使用动态库的代码在运行时才被加载到内存多个程序可以共享同一个动态库节省磁盘和内存空间。5.1 制作动态库的步骤编译源文件生成位置无关代码-fPICbashgcc -c -fPIC -o sub.o sub.c-fPIC是必需的否则无法正确生成动态库。将.o文件打包成动态库-sharedbashgcc -shared -o libsub.so sub.o # 可以同时放入多个 .o 文件动态库的命名规则lib 库名 .so。这里库名是sub。5.2 使用动态库编译程序bashgcc -o test main.o -lsub -L /path/to/libsub.so/directory-lsub告诉编译器链接libsub.so。-L指定库文件所在目录如果库不在默认路径下。5.3 运行依赖动态库的程序动态库在运行时也需要被找到。系统默认会搜索/lib、/usr/lib等目录。如果库放在其他位置有两种方法方法一把libsub.so拷贝到/lib或/usr/lib需要 root 权限。方法二设置环境变量LD_LIBRARY_PATH推荐用于测试。bashexport LD_LIBRARY_PATH$LD_LIBRARY_PATH:/path/to/your/lib ./test注意交叉编译时如果把动态库放到开发板的/lib目录下也需要设置LD_LIBRARY_PATH或修改/etc/ld.so.conf。6. 静态库的制作与使用静态库的代码在链接时被直接复制到最终的可执行文件中。好处是程序运行时不再依赖库文件缺点是生成的文件体积较大。6.1 制作静态库编译源文件得到.o文件不需要-fPICbashgcc -c -o sub.o sub.c使用ar命令打包成静态库bashar crs libsub.a sub.o # 可以同时放入多个 .o 文件c创建归档文件r向归档中插入/替换文件s创建索引加快链接速度静态库的命名规则lib 库名 .a。6.2 使用静态库编译程序bashgcc -o test main.o libsub.a如果库不在当前目录需要使用路径bashgcc -o test main.o /path/to/libsub.a6.3 运行不需要将libsub.a复制到目标机器因为它已经被合并到test可执行文件中了。动态库 vs 静态库动态库体积小更新方便替换库文件即可但运行时依赖环境。静态库体积大独立运行适合部署到环境不确定的系统。7. 很有用的 GCC 选项你可能不知道但很实用7.1 查看预处理结果宏展开、头文件路径bash# 只进行预处理输出到屏幕 gcc -E hello.c # 查看所有宏定义包括系统预定义的宏 gcc -E -dM hello.c macros.txt-dM会输出所有的#define宏非常适合调试宏相关的问题。7.2 生成依赖文件用于 Makefilebashgcc -Wp,-MD,abc.dep -c -o main.o main.c这会生成一个abc.dep文件里面记录了main.o所依赖的头文件。Makefile 可以用它来自动处理头文件变更导致的重新编译。7.3 快速查看编译器的默认头文件路径和库路径bashecho main(){} | gcc -E -v -这条命令会输出非常详细的信息包括头文件搜索路径#include ...的查找目录库文件搜索路径LIBRARY_PATH编译器的内置规范等。 在交叉编译时把gcc换成交叉编译器名字如arm-buildroot-linux-gnueabihf-gcc就能查看该交叉编译器的默认路径。8. 交叉编译特别说明当你的开发环境是 x86 PC目标运行环境是 ARM 开发板时必须使用交叉编译工具链。PC 原生编译gcc -o hello hello.c→ 生成 x86 指令只能在 PC 运行。ARM 交叉编译arm-buildroot-linux-gnueabihf-gcc -o hello hello.c→ 生成 ARM 指令可以在 ARM 板子上运行。常用的交叉编译前缀arm-linux-gnueabihf-、aarch64-linux-gnu-、arm-buildroot-linux-gnueabihf-等。8.1 交叉编译时的注意事项头文件路径交叉编译器的默认路径在其sysroot目录下。库文件路径同样在sysroot/usr/lib等位置。动态库编译时使用-l和-L运行时需要将.so文件拷贝到 ARM 板子上的/lib或设置LD_LIBRARY_PATH。静态库编译时直接链接运行时不需要库文件。8.2 交叉编译动态库的特殊要求制作 ARM 板子用的动态库时除了-shared还需要加上-fPICbasharm-buildroot-linux-gnueabihf-gcc -c -fPIC -o sub.o sub.c arm-buildroot-linux-gnueabihf-gcc -shared -o libsub.so sub.o9. 总结与速查任务命令预处理生成 .igcc -E -o hello.i hello.c编译到汇编生成 .sgcc -S -o hello.s hello.c汇编到目标文件生成 .ogcc -c -o hello.o hello.c链接生成可执行文件gcc -o hello hello.o多文件一起编译gcc -o test main.c sub.c多文件分开编译再链接gcc -c main.c; gcc -c sub.c; gcc -o test main.o sub.o指定头文件目录gcc -I ./inc -o app main.c指定库文件目录和库gcc -L ./lib -o app main.c -lmylib制作动态库gcc -shared -fPIC -o libsub.so sub.o使用动态库编译gcc -o test main.o -lsub -L ./lib运行动态库程序指定搜索路径export LD_LIBRARY_PATH$LD_LIBRARY_PATH:./lib; ./test制作静态库ar crs libsub.a sub.o使用静态库编译gcc -o test main.o libsub.a查看所有宏定义gcc -E -dM hello.c查看编译器默认搜索路径echo ‘main(){}’最后的话GCC 是一个非常强大的工具上面这些内容已经覆盖了日常开发 90% 的需求。剩下的 10% 可以在遇到具体问题时通过man gcc或搜索引擎解决。记住多动手敲命令比只看不练有效得多。