2015-05-17 21 views
15

Offenbar behandeln Compiler nach dem Stand der Technik Argumente, die vom Stack als schreibgeschützt übergeben werden. Beachten Sie, dass der Aufrufer in der x86-Aufrufkonvention Argumente auf den Stack überträgt und der Aufgerufene die Argumente im Stack verwendet. Zum Beispiel kann die folgende C-Code:x86-Aufrufkonvention: Sollen Argumente, die vom Stack übergeben werden, schreibgeschützt sein?

extern int goo(int *x); 
int foo(int x, int y) { 
    goo(&x); 
    return x; 
} 

wird durch clang -O3 -c g.c -S -m32 in OS X 10.10 in kompilierte:

.section __TEXT,__text,regular,pure_instructions 
    .macosx_version_min 10, 10 
    .globl _foo 
    .align 4, 0x90 
_foo:         ## @foo 
## BB#0: 
    pushl %ebp 
    movl %esp, %ebp 
    subl $8, %esp 
    movl 8(%ebp), %eax 
    movl %eax, -4(%ebp) 
    leal -4(%ebp), %eax 
    movl %eax, (%esp) 
    calll _goo 
    movl -4(%ebp), %eax 
    addl $8, %esp 
    popl %ebp 
    retl 


.subsections_via_symbols 

Hier wird der Parameter x (8(%ebp)) zuerst in %eax geladen; und dann in -4(%ebp) gespeichert; und die Adresse -4(%ebp) wird in %eax gespeichert; und %eax wird an die Funktion goo übergeben.

Ich frage mich, warum Clang-Code generiert, der den Wert in 8(%ebp) zu -4(%ebp) gespeichert kopieren, anstatt nur die Adresse vorbei 8(%ebp) an die Funktion goo. Es würde Speichervorgänge speichern und zu einer besseren Leistung führen. Ich habe auch in GCC ein ähnliches Verhalten beobachtet (unter OS X). Um genauer zu sein, frage ich mich, warum Compiler erzeugt nicht:

.section __TEXT,__text,regular,pure_instructions 
    .macosx_version_min 10, 10 
    .globl _foo 
    .align 4, 0x90 
_foo:         ## @foo 
## BB#0: 
    pushl %ebp 
    movl %esp, %ebp 
    subl $8, %esp 
    leal 8(%ebp), %eax 
    movl %eax, (%esp) 
    calll _goo 
    movl 8(%ebp), %eax 
    addl $8, %esp 
    popl %ebp 
    retl 


.subsections_via_symbols 

ich für Dokumente durchsucht, wenn die x86-Aufrufkonvention verlangt, um die übergebenen Argumente nur gelesen werden, aber ich kann nichts zu diesem Thema finden. Hat jemand darüber nachgedacht?

+1

Sie haben hier einen guten Punkt! '8 (% ebp)' ist im Stapelrahmen des Aufrufers, aber es ist Speicherplatz, der speziell für die Übergabe von Argumenten an 'foo' zugewiesen wurde. Wird der Aufrufer diesen Speicherplatz für seine eigenen Zwecke nutzen * nachdem * 'foo' zurückkehrt, anstatt ihn nur durch Anpassen des Stack-Zeigers zu zerstören? Wenn dies der Fall ist, ist es notwendig, den Wert in den Stapelrahmen von "foo" zu kopieren. Wenn nicht, könnte es sicher sein, dass 'foo' das Leerzeichen im Stapelrahmen des Aufrufers" borgt ", anstatt zu kopieren. Also, um zu wissen, ob deine Idee gut ist oder nicht, musst du sehen, wie der Code für 'foo's * caller * aussieht. –

+0

@AlexD Vielen Dank für Ihren Kommentar! Da 'foo' durch eine beliebige Funktion aufgerufen werden kann, denke ich, dass es sich um eine Frage zum Aufrufen von Konventionen handelt und nicht um einen spezifischen Kontext, in dem' foo' aufgerufen wird. –

+1

Das ist eine interessante Frage. Ich fand [diese andere Frage] (http://stackoverflow.com/questions/1684682/c-calling-conventions-and-passed-arguments?rq=1), die behauptet, dass gcc -O2 tatsächlich das gestoßene stack Argument des Anrufers änderte . – JS1

Antwort

6

Eigentlich habe ich kompiliert nur diese Funktion GCC:

int foo(int x) 
{ 
    goo(&x); 
    return x; 
} 

Und es erzeugt diesen Code:

_foo: 
     pushl  %ebp 
     movl  %esp, %ebp 
     subl  $24, %esp 
     leal  8(%ebp), %eax 
     movl  %eax, (%esp) 
     call  _goo 
     movl  8(%ebp), %eax 
     leave 
     ret 

Dies wird mit GCC 4.9.2 (auf 32-Bit-Cygwin, wenn es darauf ankommt), keine Optimierungen. In der Tat hat GCC genau das getan, von dem Sie dachten, dass es das tun sollte, und das Argument direkt von dem Punkt aus verwendet, an dem der Aufrufer es auf den Stapel geschoben hat.

5

Die C programming language schreibt vor, dass Argumente übergeben werden by value. Jede Änderung eines Arguments (wie zB eine x++; als erste Anweisung von foo) ist lokal für die Funktion und wird nicht an den Aufrufer weitergegeben.

Daher sollte eine allgemeine Aufrufkonvention das Kopieren von von Argumenten an jeder Aufrufstelle erfordern. Aufrufkonventionen sollten allgemein genug sein für unbekannte Anrufe, z.B. durch einen Funktionszeiger!

Natürlich, wenn Sie eine Adresse an eine Speicherzone übergeben, ist die aufgerufene Funktion frei diesen Zeiger zu dereferenzieren, z. BTW wie in

int goo(int *x) { 
    static int count; 
    *x = count++; 
    return count % 3; 
} 

, können Sie Link-Zeitoptimierungen nutzen (durch Kompilieren und Linken mit clang -flto -O2 oder gcc -flto -O2), um vielleicht den Compiler zu ermöglichen zu verbessern oder einige Anrufe zwischen Übersetzungseinheiten inline.

Beachten Sie, dass sowohl Clang/LLVM als auch free software Compiler sind. Fühlen Sie sich frei, ihnen einen Verbesserungs-Patch anzubieten, wenn Sie das möchten (aber da beide sehr komplexe Teile von Software sind, müssen Sie einige Monate arbeiten, um diesen Patch zu erstellen).

NB. Wenn Sie in den produzierten Assemblercode schauen, übergeben Sie 10 an Ihren Compiler!

+0

Da der Platz für die Argumente durch Dekrementieren von '% esp' zugewiesen wird, ist der Platz für die Argumente nicht mit dem Stackframe des Aufrufers identisch. Ich denke also, dass das Ändern der Argumente im Stack den Stackframe des Aufrufers nicht beeinflusst. –

+0

Ich bin mir nicht sicher, ob ich deinem Denken folge (es sieht so aus, als ob du die Call-by-Value-Anforderung durchbrichst). Aber zögern Sie nicht, einen Patch zu [Clang/LLVM] (http://clang.llvm.org/) oder zu [GCC] (http://gcc.gnu.org/) vorzuschlagen, wenn Sie möchten. Das würde Monate dauern. –

+0

Sie sollten Ihre Frage bearbeiten, um sie zu verbessern! –

12

Die Regeln für C sind, dass Parameter als Wert übergeben werden müssen. Ein Compiler konvertiert von einer Sprache (mit einem Regelwerk) in eine andere Sprache (möglicherweise mit einem völlig anderen Regelwerk). Die einzige Einschränkung ist, dass das Verhalten gleich bleibt.Die Regeln der Sprache C gelten nicht für die Zielsprache (z. B. Assembly).

Das bedeutet, wenn ein Compiler Lust hat, eine Assemblersprache zu erzeugen, in der Parameter als Referenz übergeben werden und nicht als Wert übergeben werden; dann ist das vollkommen legal (solange das Verhalten gleich bleibt).

Die wirkliche Einschränkung hat überhaupt nichts mit C zu tun. Die wirkliche Einschränkung ist das Verknüpfen. Damit verschiedene Objektdateien miteinander verknüpft werden können, sind Standards erforderlich, um sicherzustellen, dass der Aufrufer in einer Objektdatei Übereinstimmungen erwartet, die der Aufrufer in einer anderen Objektdatei bereitstellt. Dies ist, was als ABI bekannt ist. In einigen Fällen (z. B. 64-Bit 80 × 86) gibt es mehrere verschiedene ABIs für die exakt gleiche Architektur.

Sie können sogar Ihre eigene ABI erfinden, die radikal anders ist (und Ihre eigenen Tools implementieren, die Ihre eigenen radikal unterschiedlichen ABI unterstützen) und das ist vollkommen legal, soweit die C-Standards es erlauben; selbst wenn Ihr ABI für alles "durch Referenz" verlangt (solange das Verhalten gleich bleibt).