2009-12-02 12 views
10

In C++ sind lokale Variablen immer auf dem Stapel zugeordnet. Der Stapel ist ein Teil des zulässigen Speichers, den Ihre Anwendung belegen kann. Dieser Speicher wird in Ihrem RAM gespeichert (wenn nicht auf die Festplatte ausgelagert). Erstellt ein C++ - Compiler immer Assembler-Code, der lokale Variablen auf dem Stack speichert?C++ - CPU-Register Verwendung

Nehmen wir zum Beispiel die folgenden einfachen Code:

int foo(int n) { 
    return ++n; 
} 

In MIPS Assembler-Code, dies könnte wie folgt aussehen:

foo: 
addi $v0, $a0, 1 
jr $ra 

Wie Sie sehen können, habe ich nicht brauchen benutze den Stapel überhaupt für n. Würde der C++ - Compiler dies erkennen und direkt die CPU-Register verwenden?

Edit: Wow, vielen Dank für Ihre fast sofortige und umfassende Antworten! Der Funktionskörper von foo sollte natürlich return ++n; sein, nicht return n++;. :)

+0

Der Compiler würde optimieren. Versuchen Sie 'gcc -fverbose-asm -O2 -S yoursource.c' dann schauen Sie nach' yoursource.s' –

Antwort

9

Haftungsausschluss: Ich weiß nicht, MIPS, aber ich habe einige x86 wissen, und ich denke, das Prinzip das gleiche sein sollte ..

In der üblichen Funktion Aufrufkonvention, drückt der Compiler den Wert n auf den Stapel, um es an die Funktion foo zu übergeben. Allerdings gibt es die fastcall Konvention, die Sie verwenden können, um gcc mitzuteilen, den Wert stattdessen über die Register zu übergeben. (MSVC hat auch diese Option, aber ich bin mir nicht sicher, was die Syntax ist.

)

test.cpp:

int foo1 (int n) { return ++n; } 
int foo2 (int n) __attribute__((fastcall)); 
int foo2 (int n) { 
    return ++n; 
} 

mit g++ -O3 -fomit-frame-pointer -c test.cpp die oben Kompilieren, bekomme ich für foo1:

mov eax,DWORD PTR [esp+0x4] 
add eax,0x1 
ret 

Wie man sehen kann, liest er in dem Wert aus dem Stapel.

Und hier ist foo2:

lea eax,[ecx+0x1] 
ret 

Nun nimmt sie den Wert direkt aus dem Register.

Natürlich, wenn Sie die Funktion inline, wird der Compiler eine einfache Ergänzung in den Körper Ihrer größeren Funktion, unabhängig von der von Ihnen angegebenen Aufrufkonvention. Aber wenn Sie es nicht inline bekommen können, wird dies passieren.

Disclaimer 2: Ich sage nicht, dass Sie den Compiler immer wieder erraten sollten. Es ist wahrscheinlich in den meisten Fällen nicht praktisch und notwendig. Aber gehen Sie nicht davon aus, dass es perfekten Code produziert.

Bearbeiten 1: Wenn Sie über einfache lokale Variablen sprechen (nicht Funktionsargumente), dann ja, wird der Compiler sie in den Registern oder auf dem Stapel zuweisen, wie es passt.

Bearbeiten 2: Es scheint, dass Aufrufkonvention architekturspezifisch ist, und MIPS wird die ersten vier Argumente auf dem Stapel übergeben, wie Richard Pennington in seiner Antwort angegeben hat. In diesem Fall müssen Sie das zusätzliche Attribut nicht angeben (das tatsächlich ein x86-spezifisches Attribut ist.)

+1

-O deaktiviert Stack-Frame-Setup auf Maschinen, wo es nicht mit Debugging stören - x86 isn 't einer von ihnen, Sie brauchen einen seperaten Fokusrahmen-Zeiger, um' redundante 'Stack-Frame-Setup zu beseitigen (das ist eigentlich nützlich für das Debuggen, dh in Stack-Frame-Abwicklung) – matja

+0

yeah, das habe ich total vergessen. Ich werde es reparieren. Aber der Unterschied bleibt bestehen. – int3

+0

Ein Compiler, der Zeitoptimierungen verknüpft, kann auch erkennen, dass ein Anruf ganz allein in einen schnellen Anruf umgewandelt werden kann, da er alle Anruforte sehen und reparieren kann. –

12

Ja. Es gibt keine Regel, dass "Variablen immer auf dem Stapel zugewiesen werden". Der C++ - Standard sagt nichts über einen Stack aus. Er geht nicht davon aus, dass ein Stack existiert oder dass Register existieren. Es sagt nur, wie sich der Code verhalten soll und nicht wie er implementiert werden soll.

Der Compiler speichert Variablen nur dann auf dem Stack, wenn sie zum Beispiel einen Funktionsaufruf durchlaufen müssen oder wenn Sie versuchen, die Adresse zu übernehmen.

Der Compiler ist nicht dumm. ;)

8

Ja, ein gutes, optimierendes C/C++ wird das optimieren. Und sogar VIEL mehr: See here: Felix von Leitners Compiler Survey.

Ein normaler C/C++ - Compiler wird sowieso nicht jede Variable auf den Stack setzen. Das Problem mit Ihrer foo() Funktion könnte sein, dass die Variable über den Stack an die Funktion übergeben werden kann (die ABI Ihres Systems (Hardware/OS) definiert dies).

Mit Cs register Schlüsselwort können Sie dem Compiler einen Hinweis geben, dass es wahrscheinlich gut wäre, eine Variable in einem Register zu speichern. Beispiel:

register int x = 10; 

Aber denken Sie daran: Der Compiler frei ist nicht x in einem Register zu speichern, wenn es will!

+0

+1 für den großen Link –

6

Die Antwort ist ja, vielleicht. Dies hängt vom Compiler, der Optimierungsebene und dem Zielprozessor ab.

Bei den Mips werden die ersten vier Parameter, wenn sie klein sind, in Registern übergeben, und der Rückgabewert wird in einem Register zurückgegeben. Ihr Beispiel hat also keine Anforderung, etwas auf dem Stapel zuzuordnen.

Eigentlich ist Wahrheit seltsamer als Fiktion. In Ihrem Fall wird der Parameter unverändert zurückgegeben: der Rückgabewert der von n vor dem Operator ++:

foo: 
    .frame $sp,0,$ra 
    .mask 0x00000000,0 
    .fmask 0x00000000,0 

    addu $2, $zero, $4 
    jr  $ra 
    nop 
2

Da Ihr Beispiel foo Funktion ist eine Identitätsfunktion (es gibt nur Argument es ist), mein C++ Compiler (VS 2008) entfernt diesen Funktionsaufruf vollständig. Wenn ich es zu ändern:

int foo(int n) { 
    return ++n; 
} 

den Compiler inlines dieses mit

lea edx, [eax+1] 
+0

Ja, wieder mit einem Mips Beispiel: static int foo (int n) { Rückkehr n ++; } Int Gebühr() { Rückkehr foo (5); } gibt: .text .align 2 .globl Gebühr .ent Gebühr Gebühr: .frame sp $, 0, ra $ .mask 0x00000000,0 .fmask 0x00000000,0 ADDIU $ 2 , $ Null, 5 jr $ ra nop .set Makro .set Nachbestellung .end Gebühr .Size Gebühr. Gebühr –

0

Ja, werden die Register in C++ verwendet. Das MDR (Speicherdatenregister) enthält die Daten, die abgerufen und gespeichert werden. Um beispielsweise den Inhalt von Zelle 123 abzurufen, laden wir den Wert 123 (binär) in den MAR und führen eine Abrufoperation aus. Wenn die Operation abgeschlossen ist, würde eine Kopie des Inhalts der Zelle 123 im MDR sein. Um den Wert 98 in Zelle 4 zu speichern, laden wir eine 4 in den MAR und eine 98 in den MDR und führen einen Speicher aus. Wenn der Vorgang abgeschlossen ist, wird der Inhalt von Zelle 4 auf 98 gesetzt, indem alles, was vorher dort war, verworfen wird. Die Datenregister & arbeiten mit ihnen zusammen, um dies zu erreichen.Auch in C++, wenn wir eine Variable mit einem Wert initialisieren oder ihren Wert abfragen, tritt das gleiche Phänomen auf.

Und noch eine Sache, moderne Compiler führen auch Register Allocation, die etwas schneller als Speicherzuweisung ist.