2015-11-17 6 views
8

die folgende C++ Quellcode vor:Mit Symbol '_end' in g ++ führt zu einem Segmentation Fault

int _end[1050]; 

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

Compilation line: g++ main.cpp -o main -O0

Ausführen dieses Codes führt Fehler der Segmentierung, wenn gcc-4.8 verwenden. 4 und clang-3.6.0 unter Ubuntu 14.04. Das seltsame Verhalten ist, dass das Symbol _end auf das Ende eines statisch zugewiesenen Arrays zeigt _end, nicht an seinem Anfang. Wenn wir _end durch end_ ersetzen, funktioniert alles gut.

$ 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) 
:

Außerdem, wenn wir gcc zur Ausgabe eines Assembler-Code fragen durch -S Befehlszeilenargument bereitstellt, wird es mit „_end“ und die Version mit einem anderen Array-Name keinen signifikanten Unterschied zwischen der Version sein

Aber wenn wir die ausführbaren Dateien dump verwenden objdump und gegen sie diff laufen, werden wir, dass in der _end Version der verwendeten Adresse sehen ist 4200 = 4 * 1050 Bytes weiter als nötig:

$ 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) 

Soweit Ich weiß, GCC-Compiler kann Variab behandeln les beginnt mit Unterstrichen wie es will, ich. e. Dies ist eine schlechte Methode, um solche Symbole in Ihrem Code zu verwenden. Aber meine Frage ist: Was passiert hier wirklich? Warum wird _end durch eine Adresse des Endes eines zugewiesenen Arrays ersetzt? Warum gibt es keinen Unterschied, wenn wir das Befehlszeilenargument "-S" verwenden, aber es gibt tatsächlich einen Unterschied in den erstellten Binärdateien? Nicht dass sich gcc und clang in diesem Fall identisch verhalten, das ist mir auch fremd.

Antwort

2

Token, die mit _ beginnen, sind reserviert, und Sie sollten sie nicht verwenden. Es scheint, dass _end ein externes Symbol ist, das für unter Linux kompilierte Programme definiert ist und die erste Adresse nach dem Ende des nicht initialisierten Datensegments (auch als BSS-Segment bezeichnet) darstellt.

Hinweis: Bei einigen Systemen die Namen dieser Symbole durch Unterstrichen vorangestellt, also: _etext, _edata und _end.

Quelle: http://man7.org/linux/man-pages/man3/end.3.html

+0

Genau das, was ich brauchte, danke! Aber warum "-S" Kommandozeilenargument zeigt nichts verdächtiges beim Kompilieren dieses Codes? –

+0

@MaximAkhmedov Das liegt wahrscheinlich daran, dass '_end' ein Zeiger ist wie jeder andere Zeiger, und Zeigerarithmetik wird ausgeführt, wenn Sie Ihrem Array zuweisen. – vsoftco

0

C99 N1256 standard draft 7.1.3 "Reservierte Bezeichner", sagt:

Alle Bezeichner, die mit einem Unterstrich beginnen immer reserviert für den Einsatz als Bezeichner mit Dateiumfang in Sowohl die gewöhnlichen Namen als auch die Tag-Namen.

Dann müssen wir das wissen:

  • Datei Spielraum für Globals ist (die anderen sind Funktion und blockieren scope)
  • gewöhnlichen Namensraum enthält Variablen

So nach C99 Sie können den Bezeichner _end nicht verwenden.

Sie Implementierung

Jetzt zu sehen, warum es nicht tatsächlich auf Ihrer Implementierung, Nutzung:

g++ -Wl,--verbose main.c 

verwendet, um die Linker-Skript zu sehen.

auf Ubuntu 15.10 definiert es das Symbol _end am Ende des Datenabschnitts:

_end = .; PROVIDE (end = .); 
. = DATA_SEGMENT_END (.); 

so ist es kein Wunder, dass Art und Weise vor sich Zugriff auf den Speicher segfault kann.