gcc – 在g中使用符號’_end’會導致分段錯誤

考慮以下c原始碼:

int _end[1050];

int main() {
    for (int i = 0; i < 1050; i++)
        _end[i] = 0;
    return 0;
}

編譯行:g main.cpp -o main -O0

在Ubuntu 14.04下使用gcc-4.8.4和clang-3.6.0時,執行此程式碼會導致分段錯誤.奇怪的行為是符號_end指向靜態分配的陣列_end的末尾,而不是它的開頭.如果我們用end_替換_end,一切正常.

此外,如果我們要求gcc通過提供-S命令列引數輸出彙編程式碼,則帶有“_end”的版本與具有任何其他陣列名稱的版本之間沒有顯著差異:

$g++ main.cpp -o main.s -O0 -S
$g++ main2.cpp -o main2.s -O0 -S
$diff main.s main2.s
1,2c1,2
<   .file   "main.cpp"
<   .globl  _end
---
>   .file   "main2.cpp"
>   .globl  end_
5,7c5,7
<   .type   _end, @object
<   .size   _end, 4200
< _end:
---
>   .type   end_, @object
>   .size   end_, 4200
> end_:
25c25
<   movl    $0, _end(,%rax,4)
---
>   movl    $0, end_(,%rax,4)

但是如果我們使用objdump來轉儲可執行文件並對它們執行diff,我們將會看到在_end版本中使用的地址比所需的地址多4200 = 4 * 1050位元組:

$g++ main.cpp -o main -O0
$g++ main2.cpp -o main2 -O0
$objdump -d main >main.dump
$objdump -d main2 > main2.dump
$diff main.dump main2.dump
2c2
< main:     формат файла elf64-x86-64    // "File format" in Russian
---
> main2:     формат файла elf64-x86-64
123c123
<   4004ff: c7 04 85 c8 20 60 00    movl   $0x0,0x6020c8(,%rax,4)
---
>   4004ff: c7 04 85 60 10 60 00    movl   $0x0,0x601060(,%rax,4)

據我所知,gcc編譯器可能會根據需要處理以下劃線開頭的變數,i.即在程式碼中使用這些符號是一種不好的做法.但我的問題是:這裡到底發生了什麼?為什麼_end被替換為已分配陣列末尾的地址?如果我們使用“-S”命令列引數,為什麼沒有區別,但建立的二進位制文件實際上有區別?在這種情況下,gcc和clang的表現並不一樣,這對我來說也很奇怪.

以_開頭的標記是保留的,您不應該使用它們.似乎_end是為在Linux上編譯的程序定義的外部符號,並且表示在未初始化資料段(也稱為BSS段)結束之後的第一個地址.

Note: On some systems the names of these symbols are preceded by
underscores, thus: _etext, _edata, and _end.

資料來源: http://man7.org/linux/man-pages/man3/end.3.html

翻譯自:https://stackoverflow.com/questions/33760923/using-symbol-end-in-g-leads-to-a-segmentation-fault