跳轉到內容

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_

[編輯 | 編輯原始碼]
華夏公益教科書