Linux 应用程序调试技术/编译器
外观
当要求优化时(-O2及以上),编译器被授予“完全许可”来修改它:将未定义的行为(根据标准)视为不可能发生。因此,生成的代码在发布优化模式下的行为与调试模式不同。这种行为上的差异无法通过静态分析器或代码审查发现。
1. 这是 C99 未定义的行为,编译器假设不会发生溢出。因此,所有溢出检查都被丢弃,以下在优化代码中是一个无限循环
int i, j=0;
for (i = 1; i > 0; i += i) {
++j;
}
2. 以下
(i * 2000) / 1000
被优化成
i * 2
- 使用-Wstrict-overflow=N与N>=3来信号代码被优化掉的情况。
- 使用-fwrapv
如果足够幸运地使用 gcc 4.9 及更高版本,可以使用ubsan 净化器
- 使用-fsanitize=undefined选项编译和链接程序
- 根据需要使用其他可用标志。
编译器假设无符号整数不会环绕。将无符号变量保持在范围内[INT_MIN/2, INT_MAX/2]以避免意外。
该memset()调用可能会被移除,因为编译器认为 buf 在代码中该点之后未使用,并且在
void do_something(void {
char buf[123];
... use buf...
/* Clear it. But removed by gcc: */
memset(buf, 0, sizeof(buf));
}
- 使用#pragma optimize()指令之后强制插入代码。
- 使用volatile.
代码可以被移动
volatile int flag = 0;
char buf[123];
void init_buf() {
for (size_t i=0; i<123; ++i) {
buf[i] = 0; //Do something
}
flag = 1; // Can move!
}
可以被优化成
volatile int flag = 0;
char buf[123];
void init_buf() {
flag = 1; // Moved!
for (size_t i=0; i<123; ++i) {
//Do something
}
}
- 使用编译器内部屏障来阻止标志移动。
循环可以被优化成只有一个读取调用
void *ptr = address;
while ( *((volatile int*)ptr) & flag ) {}
- 使用restrict
- 使用-fno-delete-null-pointer-checks. 如果空指针检查放在指针第一次使用之后,它们会被删除。