Frage

In C ++ werden lokale Variablen immer auf dem Stapel zugewiesen. Der Stapel ist Teil des zulässigen Speichers, den Ihre Anwendung belegen kann. Dieser Speicher wird in Ihrem RAM gehalten (wenn nicht auf die Festplatte ausgetauscht). Erstellt ein C ++ - Compiler immer Assembler -Code, der lokale Variablen auf dem Stapel speichert?

Nehmen wir zum Beispiel den folgenden einfachen Code:

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

Im MIPS -Assembler -Code könnte dies so aussehen:

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

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

Bearbeiten: Wow, vielen Dank für Ihre fast unmittelbaren und umfangreichen Antworten! Der Funktionsbestand von Foo sollte natürlich sein return ++n;, nicht return n++;. :)

War es hilfreich?

Lösung

Haftungsausschluss: Ich kenne MIPS nicht, aber ich kenne einige x86, und ich denke, das Prinzip sollte das gleiche sein.

In der üblichen Funktionsaufrufkonvention drückt der Compiler den Wert von n auf den Stapel, um ihn an die Funktion zu übergeben foo. Es gibt jedoch die fastcall Konvention, mit der Sie GCC anweisen können, den Wert stattdessen durch die Register zu bestehen. (MSVC hat auch diese Option, aber ich bin mir nicht sicher, was seine Syntax ist.)

test.cpp:

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

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

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

Wie Sie sehen können, liest es den Wert aus dem Stapel.

Und hier ist foo2:

lea eax,[ecx+0x1]
ret

Jetzt nimmt der Wert direkt aus dem Register.

Wenn Sie die Funktion einlegen, wird der Compiler natürlich unabhängig von der von Ihnen angegebenen aufgerufenen Konvention eine einfache Ergänzung im Körper Ihrer größeren Funktion durchführen. Aber wenn Sie es nicht einbringen können, wird dies passieren.

Haftungsausschluss 2: Ich sage nicht, dass Sie den Compiler ständig auf den Markt bringen sollten. Es ist in den meisten Fällen wahrscheinlich nicht praktisch und notwendig. Aber gehen Sie nicht davon aus, dass es perfekter Code erzeugt.

Bearbeiten 1: Wenn Sie über einfache lokale Variablen sprechen (keine Funktionsargumente), dann wird der Compiler sie in den Registern oder auf dem Stapel zuordnen, sobald er es für richtig hält.

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

Andere Tipps

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

Der Compiler speichert nur Variablen auf dem Stapel, wenn er muss - wenn sie beispielsweise an einem Funktionsaufruf vorbei leben müssen oder wenn Sie versuchen, die Adresse davon zu nehmen.

Der Compiler ist nicht dumm. ;))

Ja, ein gutes, optimiertes C/C ++ wird dies optimieren. Und sogar VIEL mehr: Siehe hier: Felix von Leitners Compiler -Umfrage.

Ein normaler C/C ++ - Compiler setzt sowieso nicht jede Variable auf den Stapel. Das Problem mit Ihrem foo() Die Funktion könnte sein, dass die Variable über den Stapel an die Funktion übergeben werden kann (das ABI Ihres Systems (Hardware/Betriebssystem) definiert dies).

Mit C's register Schlüsselwort Sie können dem Compiler a geben Hinweis dass es wahrscheinlich gut wäre, eine Variable in einem Register zu speichern. Probe:

register int x = 10;

Aber denken Sie daran: Der Compiler kann nicht gelagert werden x in einem Register, wenn es will!

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

Bei den MIPS werden die ersten vier Parameter, falls klein, in Registern übergeben und der Rückgabewert in einem Register zurückgegeben. Ihr Beispiel hat also keine Anforderung, etwas auf dem Stapel zuzuweisen.

Tatsächlich ist die Wahrheit fremder als Fiktion. In Ihrem Fall wird der Parameter unverändert zurückgegeben: Der zurückgegebene Wert ist der von n vor dem ++ Operator:

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

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

Seit Ihrem Beispiel foo Funktion ist eine Identitätsfunktion (sie gibt nur das Argument zurück), mein C ++ - Compiler (VS 2008) entfernt diesen Funktionsaufruf vollständig. Wenn ich es ändere in:

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

Der Compiler verpflichtet dies mit

lea edx, [eax+1] 

Ja, die Register werden in C ++ verwendet. Die MDR (Speicherdatenregister) enthält die abgerufenen und gespeicherten Daten. Zum Beispiel, um den Inhalt von Zelle 123 abzurufen, würden wir den Wert 123 (in Binär) in den Mar und einen Abrufvorgang durchführen. Wenn die Operation abgeschlossen ist, würde eine Kopie des Inhalts von Zelle 123 im MDR sein. Um den Wert 98 in Zelle 4 zu speichern, laden wir einen 4 in den Mar und einen 98 in den MDR und führen ein Geschäft aus. Wenn der Betrieb abgeschlossen ist, wurde der Inhalt von Cell 4 auf 98 gesetzt, indem alles zuvor verworfen wurde. Die Daten- und Adressregister arbeiten mit ihnen zusammen, um dies zu erreichen. Wenn wir auch in C ++ einen VAR mit einem Wert initialisieren oder seinen Wert fragen, treten dieselben Phänomene auf.

Und noch eine Sache, moderne Compiler führen auch Registerallokation durch, was irgendwie schneller als Speicherzuweisung ist.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top