Funktionieren Klammern in C als Stapelrahmen?

Lesezeit: 10 Minuten

Wenn ich eine Variable innerhalb eines neuen Satzes von geschweiften Klammern erstelle, wird diese Variable in der schließenden Klammer vom Stapel entfernt oder bleibt sie bis zum Ende der Funktion hängen? Zum Beispiel:

void foo() {
   int c[100];
   {
       int d[200];
   }
   //code that takes a while
   return;
}

Werden d werden Speicher während der aufnehmen code that takes a while Sektion?

  • Meinen Sie (1) gemäß dem Standard, (2) universelle Praxis bei Implementierungen oder (3) allgemeine Praxis bei Implementierungen?

    – David Thornley

    3. Mai 2010 um 20:40 Uhr

Benutzeravatar von Kristopher Johnson
Christoph Johnson

Nein, Klammern fungieren nicht als Stapelrahmen. In C bezeichnen geschweifte Klammern nur einen Benennungsbereich, aber nichts wird zerstört, noch wird irgendetwas vom Stack entfernt, wenn die Kontrolle aus ihm herausgeht.

Als Programmierer, der Code schreibt, kann man sich das oft so vorstellen, als wäre es ein Stapelrahmen. Auf die in den geschweiften Klammern deklarierten Bezeichner kann nur innerhalb der geschweiften Klammern zugegriffen werden. Aus der Sicht eines Programmierers ist es also so, als würden sie bei der Deklaration auf den Stapel geschoben und dann beim Verlassen des Gültigkeitsbereichs abgelegt. Compiler müssen jedoch keinen Code generieren, der beim Ein-/Ausstieg irgendetwas pusht/poppt (und im Allgemeinen tun sie das auch nicht).

Beachten Sie auch, dass lokale Variablen möglicherweise überhaupt keinen Stapelplatz belegen: Sie könnten in CPU-Registern oder an einem anderen Hilfsspeicherort gehalten oder vollständig wegoptimiert werden.

Also, die d array könnte theoretisch Speicher für die gesamte Funktion verbrauchen. Der Compiler kann es jedoch wegoptimieren oder seinen Speicher mit anderen lokalen Variablen teilen, deren Verwendungslebensdauer sich nicht überschneidet.

  • Ist das nicht implementierungsspezifisch?

    – Avakar

    3. Mai 2010 um 16:06 Uhr

  • In C++ wird der Destruktor eines Objekts am Ende seines Gültigkeitsbereichs aufgerufen. Ob der Speicher zurückgefordert wird, ist ein implementierungsspezifisches Problem.

    – Christoph Johnson

    3. Mai 2010 um 16:07 Uhr

  • @pm100: Die Destruktoren werden aufgerufen. Das sagt nichts über die Erinnerung aus, die diese Objekte belegten.

    – Donal Fellows

    3. Mai 2010 um 16:12 Uhr

  • Der C-Standard legt fest, dass sich die Lebensdauer von im Block deklarierten automatischen Variablen nur so lange erstreckt, bis die Ausführung des Blocks endet. Also im Wesentlichen diese automatischen Variablen tun am Ende des Blocks “zerstört” werden.

    – Café

    4. Mai 2010 um 1:18 Uhr


  • @KristopherJohnson: Wenn eine Methode zwei separate Blöcke hätte, von denen jeder ein 1-KByte-Array deklariert, und einen dritten Block, der eine verschachtelte Methode aufruft, könnte ein Compiler denselben Speicher für beide Arrays verwenden und/oder das Array platzieren am flachsten Teil des Stapels und bewegen Sie den Stapelzeiger darüber, um die verschachtelte Methode aufzurufen. Ein solches Verhalten könnte die für den Funktionsaufruf erforderliche Stack-Tiefe um 2 KB reduzieren.

    – Superkatze

    7. Januar 2014 um 22:36 Uhr

Benutzeravatar von caf
Café

Die Zeit, während der die Variable ist eigentlich Die Speicherbelegung ist offensichtlich vom Compiler abhängig (und viele Compiler passen den Stapelzeiger nicht an, wenn innere Blöcke innerhalb von Funktionen betreten und verlassen werden).

Eine eng verwandte, aber möglicherweise interessantere Frage ist jedoch, ob das Programm auf dieses innere Objekt außerhalb des inneren Gültigkeitsbereichs (aber innerhalb der enthaltenden Funktion) zugreifen darf, dh:

void foo() {
   int c[100];
   int *p;

   {
       int d[200];
       p = d;
   }

   /* Can I access p[0] here? */

   return;
}

(Mit anderen Worten: ist der Compiler erlaubt aufzuheben dauch wenn die meisten es in der Praxis nicht tun?).

Die Antwort ist, dass der Compiler ist freigeben dürfen dund zugreifen p[0] wobei der Kommentar ein undefiniertes Verhalten anzeigt (das Programm ist nicht auf das innere Objekt außerhalb des inneren Bereichs zugreifen dürfen). Der relevante Teil des C-Standards ist 6.2.4p5:

Für so ein Objekt [one that has
automatic storage duration] das keinen Array-Typ mit variabler Länge hat,
seine Lebensdauer erstreckt sich vom Eintritt in den Block, dem er zugeordnet ist, bis die Ausführung dieses Blocks in irgendeiner Weise endet. (Das Betreten eines eingeschlossenen Blocks oder das Aufrufen einer Funktion unterbricht die Ausführung des aktuellen Blocks, beendet sie jedoch nicht.) Wenn der Block rekursiv betreten wird, wird jedes Mal eine neue Instanz des Objekts erstellt. Der Anfangswert des Objekts ist unbestimmt. Wenn für das Objekt eine Initialisierung angegeben ist, wird sie jedes Mal durchgeführt, wenn die Deklaration in der Ausführung des Blocks erreicht wird; Andernfalls wird der Wert jedes Mal unbestimmt, wenn die Deklaration erreicht wird.

  • Als jemand, der lernt, wie Umfang und Speicher in C und C++ funktionieren, nachdem ich jahrelang höhere Sprachen verwendet habe, finde ich diese Antwort präziser und nützlicher als die akzeptierte.

    – Chris

    10. Februar 2018 um 23:31 Uhr

Ihre Frage ist nicht klar genug, um eindeutig beantwortet zu werden.

Auf der einen Seite führen Compiler normalerweise keine lokale Speicherzuweisung und -freigabe für verschachtelte Blockbereiche durch. Der lokale Speicher wird normalerweise nur einmal beim Funktionseintritt allokiert und beim Funktionsaustritt freigegeben.

Wenn andererseits die Lebensdauer eines lokalen Objekts endet, kann der von diesem Objekt belegte Speicher später für ein anderes lokales Objekt wiederverwendet werden. Zum Beispiel in diesem Code

void foo()
{
  {
    int d[100];
  }
  {
    double e[20];
  }
}

Beide Arrays belegen normalerweise denselben Speicherbereich, was bedeutet, dass die Gesamtmenge des lokalen Speichers von der Funktion benötigt wird foo ist alles Notwendige für die größten von zwei Arrays, nicht für beide gleichzeitig.

Ob letzteres in Frage kommt d Die weitere Belegung des Speichers bis zum Ende der Funktion im Kontext Ihrer Frage liegt bei Ihnen.

Es ist implementierungsabhängig. Ich habe ein kurzes Programm geschrieben, um zu testen, was gcc 4.3.4 macht, und es weist beim Start der Funktion den gesamten Stack-Speicherplatz auf einmal zu. Sie können die von gcc erstellte Assembly mit dem Flag -S untersuchen.

Benutzeravatar von Joseph Quinsey
Josef Quinsey

Nicken[] Wille nicht für den Rest der Routine auf dem Stack sein. Aber alloca() ist anders.

Bearbeiten: Kristopher Johnson (und Simon und Daniel) sind Rechtsund meine erste Antwort war falsch. Mit gcc 4.3.4.on CYGWIN der Code:

void foo(int[]);
void bar(void);
void foobar(int); 

void foobar(int flag) {
    if (flag) {
        int big[100000000];
        foo(big);
    }
    bar();
}

gibt:

_foobar:
    pushl   %ebp
    movl    %esp, %ebp
    movl    $400000008, %eax
    call    __alloca
    cmpl    $0, 8(%ebp)
    je      L2
    leal    -400000000(%ebp), %eax
    movl    %eax, (%esp)
    call    _foo
L2:
    call    _bar
    leave
    ret

Lebe und lerne! Und ein Schnelltest scheint zu zeigen, dass AndreyT auch bei Mehrfachzuweisungen Recht hat.

Viel später hinzugefügt: Der obige Test zeigt die gcc-Dokumentation ist nicht ganz richtig. Seit Jahren heißt es (Hervorhebung hinzugefügt):

“Der Platz für ein Array variabler Länge ist freigegeben sobald der Array-Name ist Umfang endet.”

  • Das Kompilieren mit deaktivierter Optimierung zeigt Ihnen nicht unbedingt, was Sie an optimiertem Code erhalten. In diesem Fall ist das Verhalten dasselbe (beim Start der Funktion zuweisen und erst beim Verlassen der Funktion freigeben): godbolt.org/g/M112AQ. Aber Nicht-Cygwin-gcc ruft keine an alloca Funktion. Ich bin wirklich überrascht, dass cygwin gcc das tun würde. Es ist nicht einmal ein Array mit variabler Länge, also IDK, warum Sie das ansprechen.

    – Peter Cordes

    30. Juli 2017 um 2:27 Uhr

Benutzeravatar von user19666
Benutzer19666

Sie könnten. Vielleicht nicht. Die Antwort, die Sie meiner Meinung nach wirklich brauchen, lautet: Nehmen Sie niemals etwas an. Moderne Compiler vollbringen alle Arten von architektur- und implementierungsspezifischer Magie. Schreiben Sie Ihren Code einfach und lesbar für Menschen und lassen Sie den Compiler die guten Sachen machen. Wenn Sie versuchen, um den Compiler herum zu programmieren, fragen Sie nach Ärger – und der Ärger, den Sie normalerweise in diesen Situationen bekommen, ist normalerweise schrecklich subtil und schwer zu diagnostizieren.

  • Das Kompilieren mit deaktivierter Optimierung zeigt Ihnen nicht unbedingt, was Sie an optimiertem Code erhalten. In diesem Fall ist das Verhalten dasselbe (beim Start der Funktion zuweisen und erst beim Verlassen der Funktion freigeben): godbolt.org/g/M112AQ. Aber Nicht-Cygwin-gcc ruft keine an alloca Funktion. Ich bin wirklich überrascht, dass cygwin gcc das tun würde. Es ist nicht einmal ein Array mit variabler Länge, also IDK, warum Sie das ansprechen.

    – Peter Cordes

    30. Juli 2017 um 2:27 Uhr

Ihre Variable d wird normalerweise nicht vom Stack entfernt. Geschweifte Klammern bezeichnen keinen Stapelrahmen. Sonst könntest du so etwas nicht machen:

char var = getch();
    {
        char next_var = var + 1;
        use_variable(next_char);
    }

Wenn geschweifte Klammern einen echten Stack-Push/Pop verursachen (wie es ein Funktionsaufruf tun würde), würde der obige Code nicht kompiliert, da der Code in den geschweiften Klammern nicht auf die Variable zugreifen könnte var die sich außerhalb der geschweiften Klammern befindet (so wie eine Unterfunktion nicht direkt auf Variablen in der aufrufenden Funktion zugreifen kann). Wir wissen, dass dies nicht der Fall ist.

Geschweifte Klammern werden einfach zum Scoping verwendet. Der Compiler behandelt jeden Zugriff auf die “innere” Variable von außerhalb der einschließenden geschweiften Klammern als ungültig und verwendet diesen Speicher möglicherweise für etwas anderes (dies ist von der Implementierung abhängig). Es darf jedoch nicht vom Stack entfernt werden, bis die einschließende Funktion zurückkehrt.

Aktualisieren: Hier ist, was die C-Spez muss sagen. Zu Objekten mit automatischer Speicherdauer (Abschnitt 6.4.2):

Für ein Objekt, das keinen Array-Typ mit variabler Länge hat, erstreckt sich seine Lebensdauer vom Eintritt in den Block, dem es zugeordnet ist, bis die Ausführung dieses Blocks ohnehin endet.

Derselbe Abschnitt definiert den Begriff “lebenslang” als (Hervorhebung von mir):

Das Lebensdauer eines Objekts ist der Teil der Programmausführung, während dessen die Speicherung erfolgt garantiert dafür reserviert werden. Ein Objekt existiert, hat eine konstante Adresse und behält während seiner gesamten Lebensdauer seinen zuletzt gespeicherten Wert. Wenn auf ein Objekt außerhalb seiner Lebensdauer verwiesen wird, ist das Verhalten undefiniert.

Das Schlüsselwort hier ist natürlich „garantiert“. Sobald Sie den Geltungsbereich der inneren geschweiften Klammern verlassen, ist die Lebensdauer des Arrays abgelaufen. Es kann Speicher zugewiesen werden oder auch nicht (Ihr Compiler verwendet den Speicherplatz möglicherweise für etwas anderes), aber alle Versuche, auf das Array zuzugreifen, rufen ein undefiniertes Verhalten hervor und führen zu unvorhersehbaren Ergebnissen.

Die C-Spezifikation hat keine Vorstellung von Stack-Frames. Es spricht nur darüber, wie sich das resultierende Programm verhalten wird, und überlässt die Implementierungsdetails dem Compiler (schließlich würde die Implementierung auf einer stapellosen CPU ganz anders aussehen als auf einer CPU mit einem Hardware-Stack). Es gibt nichts in der C-Spezifikation, das vorschreibt, wo ein Stack-Frame endet oder nicht. Das einzige real Der Weg zu wissen ist, den Code auf Ihrem speziellen Compiler/Plattform zu kompilieren und die resultierende Assembly zu untersuchen. Dabei spielen wahrscheinlich auch die aktuellen Optimierungsoptionen Ihres Compilers eine Rolle.

Wenn Sie sicherstellen möchten, dass das Array d keinen Speicher mehr verbraucht, während Ihr Code ausgeführt wird, können Sie den Code entweder in geschweiften Klammern in eine separate Funktion umwandeln oder explizit malloc und free den Speicher, anstatt die automatische Speicherung zu verwenden.

  • “Wenn geschweifte Klammern einen Stack-Push/Pop verursachen, würde der obige Code nicht kompiliert, da der Code in den geschweiften Klammern nicht auf die Variable var zugreifen könnte, die sich außerhalb der geschweiften Klammern befindet.” – das stimmt einfach nicht. Der Compiler kann sich immer den Abstand vom Stack/Frame-Zeiger merken und ihn verwenden, um auf äußere Variablen zu verweisen. Siehe auch Josephs Antwort für ein Beispiel von geschweiften Klammern tun einen Stack-Push/Pop verursachen.

    – George

    8. Februar 2012 um 20:17 Uhr


  • @george- Das von Ihnen beschriebene Verhalten sowie das Beispiel von Joseph hängen vom verwendeten Compiler und der verwendeten Plattform ab. Beispielsweise führt das Kompilieren desselben Codes für ein MIPS-Ziel zu völlig anderen Ergebnissen. Ich habe nur aus der Sicht der C-Spezifikation gesprochen (da das OP keinen Compiler oder Ziel angegeben hat). Ich werde die Antwort bearbeiten und weitere Einzelheiten hinzufügen.

    – bta

    14. Februar 2012 um 18:48 Uhr

1424590cookie-checkFunktionieren Klammern in C als Stapelrahmen?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy