跳转到内容

Linux 应用程序调试技术/编译器

来自维基教科书,开放的书,为一个开放的世界

当要求优化时(-O2及以上),编译器被授予“完全许可”来修改它:将未定义的行为(根据标准)视为不可能发生。因此,生成的代码在发布优化模式下的行为与调试模式不同。这种行为上的差异无法通过静态分析器或代码审查发现。

带符号整数溢出

[编辑 | 编辑源代码]

1. 这是 C99 未定义的行为,编译器假设不会发生溢出。因此,所有溢出检查都被丢弃,以下在优化代码中是一个无限循环

int i, j=0;
for (i = 1; i > 0; i += i) {
    ++j;
}

2. 以下

    (i * 2000) / 1000

被优化成

    i * 2
  • 使用-Wstrict-overflow=NN>=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陷阱

[编辑 | 编辑源代码]

代码可以被移动

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. 如果空指针检查放在指针第一次使用之后,它们会被删除。


_STD_ANALYZABLE_

[编辑 | 编辑源代码]
华夏公益教科书