In dem x86-64 System V ABI, Funktion args werden in Registern übergeben. (Links zu anderen ABI-Dokumenten und Erklärungen zu einer ABI-Dokumentation finden Sie unter x86 tag wiki.)
Sie haben nur Adressen überhaupt, weil gcc -O0
sie an den Stapel verschüttet. Dies macht das Debuggen von C/C++ einfacher/konsistenter: alles hat eine Adresse, und der dort gespeicherte Wert ist nach jeder C-Anweisung immer aktuell. Es macht jedoch schrecklich ineffizienten ASM-Code. gcc -Og
ist nicht so strikt bei der Speicherung in den Speicher, so dass Sie manchmal "Wert optimiert aus", aber es ist immer noch "für das Debuggen optimiert". Das andere Ziel von gcc -O0
ist, schnell zu kompilieren, nicht, um guten Code zu machen. Wundern Sie sich also nicht, dass Sie nicht optimale Entscheidungen treffen, wenn Sie Locals auf dem Stack platzieren. z.B. es könnte nur 16 Bytes reserviert haben und argv
bei [rbp-16]
(8 Byte ausgerichtet), argc bei [rbp-8]
(4 Byte ausgerichtet) setzen und das 4B temporär bei [rbp-4]
halten, wie die tatsächliche Wahl von gcc5.3.
Der einzige "Grund" für die Lücke zwischen ihren tatsächlichen Speicherorten ist die innere Funktionsweise der Algorithmen von gcc zur Erstellung von Locals, bevor weitere Optimierungen durchgeführt werden.
Um zu sehen, was wirklich passiert, wenn Sie eine Funktion kompilieren, von -O3 -march=native -fverbose-asm
oder etwas an asm-Ausgang (-S
) buchen. (Tun Sie dies mit Funktionen, die Eingaben verwenden und einen Wert zurückgeben, statt Kompilierung-Konstante Eingänge, so dass sie nicht optimieren weg.)
Dies ist der Beginn der main()
, wie gcc 5.3 on the Godbolt Compiler Explorer (with -O0 -fverbose-asm
) zusammengestellt:
main:
push rbp #
mov rbp, rsp #,
sub rsp, 32 #,
mov DWORD PTR [rbp-20], edi # argc, argc
mov QWORD PTR [rbp-32], rsi # argv, argv
mov eax, DWORD PTR [rbp-20] # tmp92, argc # see how dumb gcc -O0 is: it reloads from memory instead of using the value in edi
...
Auf Funktionseingabe, edi
hält ArgC, und rsi
hält Argv. main()
's Aufrufer (der libc C-Laufzeit-Startup-Code) legen Sie sie dort. mov QWORD PTR [rbp-32], rsi
ist die Anweisung, die argv am Ende des reservierten Speicherplatzes speichert (mit sub rsp, 32
). [rbp-32]
ist zufällig die gleiche Adresse wie [rsp]
, aber da gcc die Mühe machte, einen Stapelrahmen (ist nur der Standard bei -O1
oder höher) zu machen, adressiert es Einheimische mit Offsets von rbp
.
In der 32-Bit-SysV ABI, würden diese Argumente in Erinnerung sein bereits auf dem Stapel auf Funktion Eintrag, denn das ABI leider keine Register für arg-passing zu verwenden ist. Die zusätzlichen Befehle und die Latenzzeit, die durch die zusätzlichen Weiterleitungs-Rundlaufvorgänge auferlegt werden, die von der Alt-ABI benötigt werden, sind einer der Gründe, warum 32 Bit langsamer als 64 Bit sind, sogar abgesehen von den Überläufen/Neuladungen, die durch weniger Register verursacht werden. Einige 32-Bit-Windows-ABIs verwenden 2 Regs zum Arg-Passing, z. die __vectorcall
ABI. Das ist gut, denn viele Windows-Programme werden immer noch als 32bit verbreitet. (64-Bit-Linux-Systeme in der Regel müssen keine 32-Bit-Code ausführen.)
BTW, die ABI Standarddokumente wie argc/argv/envp auf den Stapel gelegt werden für Ihren neuer execve(2)
ed Prozess, und dass Bei den meisten anderen Registern als %rsp
muss davon ausgegangen werden, dass sie Müll enthalten. d. h. die Prozess Startup-Umgebung für _start
, die sich deutlich unterscheidet, was der C-Laufzeitcode vor dem Aufruf main()
erstellt. z.B. Beim Eintrag auf _start
ist der Anfang des Stapels keine Absenderadresse, Sie können also nicht ret
. (Sie haben einen exit(2)
Systemaufruf zu machen, das ist, was schließlich passiert ist, nachdem Sie von main()
zurückzukehren.)
Siehe x86 Tag Wiki für viele andere Links zu docs/tutorials/Anfänger-Fragen
Related ? http://StackOverflow.com/Questions/1408900/Question-about-Pointer-Ausrichtung – rrauenza
Könnte verwandt sein: http://StackOverflow.com/A/37713560/257645 (erklärt, was _Start tut, die den Stapel mit ArgC/setzt argv) – kfsone