0.简介
前文已经对编译链接流程进行了介绍,本文将介绍一些实用的编译选项和工具去帮助开发者来排查错误以及优化性能。
1.段错误的检查和预防
段错误(Segmentation Fault)在编程中是非常常见的,其错误原因是程序试图访问没有分配给它的内存区域,或者是用不恰当的方式去访问内存(比如对只读内存进行写操作)。大部分的段错误比较好排查,比如空指针,栈溢出,内存越界等,但在多线程复杂环境下,很多的问题就不太好排查了,比如写越界,写到了不是自己申请的buff中,或者double free(多次释放同一块内存),再或者是释放后继续使用,这就需要借助工具了。
常见的工具就是varlgrind,但是varlgrind是通过模拟来操作,其虽然错误详细但性能较慢,所以此处介绍的是asan版本,其是通过插入检测代码和影子内存来实现,性能比较好,编译中带上-fsanitize=address选项就可以用来排查内存错误。
#include <stdlib.h>
int main() {
int *arr = malloc(10 * sizeof(int));
free(arr);
free(arr);
return 0;
}
gcc -fsanitize=address -g doublefree.c -o test
./test
当然也可以开启-Wall -Werror在编译期来检查一些简单的错误。
2.符号冲突的排查和处理
符号冲突即多个代码块中出现了同名的标识符,导致编译器或链接器不知道该使用哪一个定义了,常见的场景有以下几类:
1)自己内部代码中同名的全局变量冲突:两个.o定义了同名的变量,可以修改一个为静态变量限制作用域或者修改变量名。
2)静态库代码冲突:两个静态库中符号冲突,除了代码修改之外,还可以使用objcopy工具去移除符号或者将符号改为本地(LOCAL),也可以使用--exclude-libs去排除符号。
3)动态库符号冲突:这个也可以使用和静态库处理的类似的方式,当然这个还需要根据实际情况处理,下面会推荐一些常用工具。
工具 | 作用 |
nm | 查看目标文件符号 |
readelf | 分析ELF文件结构 |
ldd | 检查动态库依赖 |
LD_DEBUG | 动态链接器调试 |
3.性能优化参数
优化等级 | 适用场景 |
O0 | 调试 |
O2 | 生产环境(平衡性能和大小) |
O3 | 极致性能 |
LTO | 跨模块优化 |
# 编译阶段生成LTO中间代码
gcc -flto -c module1.c module2.c
# 链接阶段应用优化
gcc -flto -o app module1.o module2.o
4.预防问题方式介绍
4.1 防御参数检查
# 开启所有警告并视作错误
gcc -Wall -Wextra -Werror -pedantic
# 防止符号冲突
-fvisibility=hidden
# 堆栈保护
-fstack-protector-strong
4.2 自动化检查脚本示例
#!/bin/bash
# 检查未定义符号
if nm -u $1 | grep "U "; then
echo " 存在未定义符号!"
nm -u $1 | grep "U "
fi
# 检查动态库依赖
ldd $1 | grep "not found" && echo " 缺失动态库!"
5.总结
本文讲解了在实际编程中常见的问题以及常见的处理方式和工具,下一篇将是本系列的最后一篇,介绍LLVM交叉编译。