令人驚訝的未定義行為 - signed integer overflow, nullptr dereferencing

我是從 PTT 上這篇看到的

有限迴圈變成無限迴圈

(Undefined Behavior - signed integer overflow)

int main() {
  char buf[50] = "y";
  for (int j = 0; j < 9; ++j) {
    std::cout << (j * 0x20000001) << std::endl;
    if (buf[0] == 'x') break;
  }
}

Compiler Explorer (Originally shared by Eric Musser)
gcc-11.2 -O3 會造成無窮迴圈,但 -O0 就正常
因為這個有 Undefined Behavior - signed integer overflow
首先,編譯器會將之變成

for (int p = 0; p < 9 * 0x20000001; p += 0x20000001) {
  std::cout << p << std::endl;
  if (buf[0] == 'x') break;
}

然而,編譯器可以假設 overflow 的情形不會發生,又 9 * 0x20000001 > INT_MAX,所以該條件恆成立

for (int p = 0; true; p += 0x20000001) {...}

從而導致無窮迴圈

static function 沒有給定但卻給定值了

(Undefined Behavior - nullptr dereferencing)

typedef int (*Function)();

static Function Do;

static int EraseAll() {
  std::cout << "Disaster Ahead" << std::endl;
  // system("rm -rf /");
  return 0;
}

void NeverCalled() {
  Do = EraseAll;  
}

int main() {
  return Do();
}

Compiler Explorer (Originally shared by Krister Walfridsson)

Clang-10.0.0 會輸出 “Disaster Ahead” 即便我們沒有使用 NeverCalled()Do 設為 EraseAll,他還是將之設為 EraseAll 了,如果沒有將 rm -rf 註記掉,他就會將東西刪光光了。

該程式有 Undefined Behavior - nullptr dereferencing
Do 只有兩個可能性 nullptr 或者是 EraseAll
因為當 Donullptr 時,程式本身就會有未定義行為
編譯器因此假設 EraseAll 是唯一可能的選項,並優化一開始就設為 EraseAll;而不是一開始是 nullptr 然後再設為 EraseAll

因此

void NeverCalled() { Do = ...; } ->
void NeverCalled() { } // empty

int main() { return Do(); } ->
int main() { return EraseAll(); }

導致出現 “Disaster Ahead”

1個讚