Linux----gcc、g++的使用以及一些问题
程序生成过程
在学习C语言的时候,我们知道,程序的生成需要经过预处理、编译、汇编、链接。
预处理
预处理时主要就是展开头文件、处理条件编译、删除注释、处理宏。我们来看一段代码(centos 7.6):
在这里直说了,用gcc/g++编译程序时,直接g++ 文件名,就会生成一个a.out的可执行文件,./a.out就可以执行它。但是现在我们要看预处理后的代码是怎样的,执行下面的指令会生code.i,他是预处理后的代码,如果不用-o并且重命名,那么会直接把预处理后的代码打印到终端:
用vim打开code.i观看预处理后的代码,这是代码头部:
这是到最后,可以看到多出来了一万多行代码,这都是展开的头文件,可以看到处理了宏定义,删除了注释,处理了条件编译:
编译
执行下面指令生成编译后的代码
vim打开,发现都是汇编语言。编译把预处理后的 C/C++ 源码转成 汇编代码。语法检查、优化都在这一步完成。输出是 .s
汇编文件。
汇编
虽然生成了.o目标文件,但是仍然还不能执行,为什么呢?因为虽然code.cpp变成了code.o,但是其中调用的一些库函数仍然找不到他们各自的定义。比如可能会调用printf,这时编译器只知道“有个叫 printf 的函数”,但没有它的地址。这些符号要等到 链接阶段 才能被解析并指向正确的库函数。
链接
此时执行如下链接指令生成可执行程序,并运行:
这里就牵扯到了一个问题,什么是库呢?
库
头文件中一般只有库函数的声明,而库函数的定义在库中。这些函数实现都在名为 libc.so.6 的库文件中,g++一般会到系统默认的搜索路径“/usr/lib”下进行查找,链接libc.so.6 库函数与.o文件,这样就能实现printf函数,这里的库有两种:静态库和动态库。那么如何进行链接?
静态库
一堆 .o
目标文件打包在一起(通常扩展名是 .a
,Windows 下是 .lib
)。在编译时,编译器会把需要的函数代码 直接拷贝 到可执行程序里。可执行文件体积大(因为包含了库的代码)。程序运行时不依赖外部库文件。更新库时,用到了该库的程序都需要重新编译程序。
静态库像是 把工具书的内容直接复印到你的作业里,你交出去的作业自己就全了,不需要再带工具书。
动态库
也是一堆函数的二进制代码,但在运行时才被加载(Linux 下 .so
,Windows 下 .dll
)。可执行文件里只保存“引用”,程序启动或运行时再从系统里找到对应的库。可执行文件体积小(只存符号引用)。多个程序可以共享同一个库,节省内存。更新库文件时,不用重新编译程序,只要接口没变,程序就能用新的功能。
动态库像是 写作业时引用一本公共图书馆的工具书,你的作业本身不包含解释,但只要图书馆有这本书,就能看懂。那么当链接时链接的是动态库的话就是动态链接,反之就是静态链接。
动态链接
g++一般默认是动态链接,ldd
显示的是程序运行时需要加载的动态库,以及它们在系统里的位置,查看刚刚生成的code:
静态链接
可以用指令要求静态链接,前提是有静态库文件,可以用指令下载g++与gcc的静态库:sudo yum install glibc-static libstdc++-static。紧接着使用-static选项要求静态链接,可以看到静态链接生成的可执行文件大小特别大。
如果不指定的话,一般是动态链接优先,如果没有动态库,会调用静态库进行静态链接。
对比总结
特性 | 静态库 .a/.lib | 动态库 .so/.dll |
---|---|---|
链接时间 | 链接时拷贝进可执行文件中 | 运行时到对应库加载 |
可执行文件大小 | 大(包含库代码) | 小(只保存引用) |
运行依赖 | 不依赖外部库 | 依赖库文件存在 |
更新维护 | 改库需重新编译 | 更新库即可 |
内存占用 | 每个程序有自己的副本 | 多个程序共享一份 |
静态库:编译时打包进程序,独立但臃肿;动态库=运行时加载,共享但有依赖。
release与debug版本
可执行文件一般在发布时是release版本,程序在开发时是debug版本,形成可执行文件时添加了debug信息。默认情况下,gcc/g++ 编译出来的程序既不是严格的 Debug,也不是严格的 Release,可以理解为一个 未优化的普通构建。它只是编译器直接把源代码翻译成机器码生成可执行文件,适合快速测试和开发。
产生debug版本指令:
debug版本下因为添加了debug信息,所以要比默认的大一些。
release版本:
g++ code.cpp -O2 -DNDEBUG -o code_release
strip code_release # 可选,去掉符号表,减小体积
参数说明
-O2
→ 优化编译,平衡性能和编译时间。消除冗余代码、循环优化、内联函数等-O3
→ 更激进优化,可选。函数内联扩展等-DNDEBUG
→ 禁用assert()
断言-o code_release
→ 输出文件名strip
→ 删除符号表(删除调试信息),让可执行文件更小,但不影响执行。
Release 版本 优化执行速度,不保留调试信息,Debug 版本则相反,保留调试信息、不开优化。观察文件大小: