Kann ein Compiler abhängig vom case-Zweig pushen bzw. konstruieren?



  • Hallo, ich hätte mal eine Frage, die mir schon etwas länger unter den Nägeln brennt. Angenommen eine Funktion, die Nachrichten verarbeitet:

    void func(unsigned msg /*weitere Parameter*/)
    {
      switch(msg)
      {
        case MOUSEMOVE:
        {
          int k = /*irgendwas*/;
          break;
        }
        case INIT:
        {
          short j = /*irgendwas*/;
          break;
        }
    }
    

    Ist ein Compiler in der Lage, erst jeweils in den Zweigen abhängig von der Nachricht die nötigen Speicherbereiche auf den Stack zu pushen?
    Und wie sieht es aus, wenn j im INIT-Zweig ein std::vector<std::string> wäre? Müsste dann z.B. bei jedem MOUSEMOVE dieser vector erzeugt und destruiert werden?
    Klar ist natürlich, dass Funktionsaufrufe sauberer wären und dieses Problem umgehen würden - mir geht es aber um den allgemeinen Fall (daher auch das Erstellen in diesem Forum).

    Achja, den asm-Output habe ich mir angeschaut, werde aber diesbezüglich nicht wirklich schlau draus.



  • Stackfrager schrieb:

    Ist ein Compiler in der Lage, erst jeweils in den Zweigen abhängig von der Nachricht die nötigen Speicherbereiche auf den Stack zu pushen?

    Was heißt "in der Lage"? Ja sicher, prinzipiell schon, aber ob er es auch tut ist die Frage. Normalerweise werden einfach alle automatischen Variablen am Anfang der Funktion alloziert, das ist eine einzige Addition auf den Stackpointer. Wenn das erst bei Erreichen der Deklaration gemacht wird, spart man eventuell Speicher, verbraucht aber mehr Laufzeit. Das ist also ein Tradeoff, und ich könnte mir vorstellen, dass bei großen Objekten (Arrays ...) ein Compiler auch mal erst an Ort und Stelle alloziert.
    Normalerweise sollte dich sowas aber gar nicht kümmern. Oder hast du Probleme mit der Stackgröße?

    Und wie sieht es aus, wenn j im INIT-Zweig ein std::vector<std::string> wäre? Müsste dann z.B. bei jedem MOUSEMOVE dieser vector erzeugt und destruiert werden?

    Ja klar. Das ist durch die Sprachdefinition von C++ so vorgegeben, da gibt es keine Wahlmöglichkeit.



  • @Bashar: Bist du dir sicher, dass ein Objekt in einem Case-Zweig konstruiert wird, obwohl der Zweig nicht erreicht wird? Stackfrager hat doch schon extra einen neuen Block durch seine geschweiften Klammern angefangen. Was sollte den Compiler daran hindern, in den INIT-Case-Zweig einen Konstruktor- und einen Destruktoraufruf einzubauen?

    Außerdem glaube ich auch nicht, dass schon am Anfang der Funktion der Stack-Pointer erhöht wird, weil der Compiler gar nicht weiß, wie viel Stack benötigt wird. Außerdem würde bei std::vector sowieso nicht viel Stack benötigt, da er seinen Speicher auf dem Heap anlegt.


  • Mod

    wxSkip schrieb:

    @Bashar: Bist du dir sicher, dass ein Objekt in einem Case-Zweig konstruiert wird, obwohl der Zweig nicht erreicht wird? Stackfrager hat doch schon extra einen neuen Block durch seine geschweiften Klammern angefangen. Was sollte den Compiler daran hindern, in den INIT-Case-Zweig einen Konstruktor- und einen Destruktoraufruf einzubauen?

    Bei int gibt es ja nix aufzurufen. Und Konstruktor und Destruktoraufrufe werden natürlich verzögert durchgeführt (wenn es Vorteile bringt), die Stackallocation ist aber völlig unabhängig vom Konstruktoraufruf. Der Konstruktor bekommt bloß eine Speicherstelle mitgeteilt, auf der er arbeiten soll, wann die allokiert wurde ist Wurscht.

    Außerdem glaube ich auch nicht, dass schon am Anfang der Funktion der Stack-Pointer erhöht wird, weil der Compiler gar nicht weiß, wie viel Stack benötigt wird. Außerdem würde bei std::vector sowieso nicht viel Stack benötigt, da er seinen Speicher auf dem Heap anlegt.

    Er kann sich doch alle Scopes zusammenzählen und dann am Anfang einer Funktion alle Variablen zusammen auf den Stack legen. Es müssen ja nicht alle Variablen benutzt werden. Es muss auch nicht jeder Bereich auf dem Stack ständig der gleichen Variablen entsprechen. Oder eine Variable braucht gar nicht auf dem Stack zu liegen. Da besteht ungeheuer viel Optimierungspotential, dass sich der Compiler bestimmt nicht wegen ein paar Klammern mit rein syntaktischer Funktion wird nehmen lassen.



  • SeppJ schrieb:

    Er kann sich doch alle Scopes zusammenzählen und dann am Anfang einer Funktion alle Variablen zusammen auf den Stack legen.

    Nicht zusammenzählen. Der Stackbedarf des Funktionsscopes + der größte Stackbedarf der Subscopes reicht ja. In dem Beispiel eben der Speicher für ein int (plus ABI-Interna).



  • wxSkip schrieb:

    @Bashar: Bist du dir sicher, dass ein Objekt in einem Case-Zweig konstruiert wird, obwohl der Zweig nicht erreicht wird?

    Nein, sorry, ich hab nicht richtig gelesen. (Ich war in Gedanken noch bei der ersten Frage.)

    Das Objekt wird nur konstruiert, wenn der Zweig durchlaufen wird.



  • Danke für alle Antworten.

    rüdiger schrieb:

    Der Stackbedarf des Funktionsscopes + der größte Stackbedarf der Subscopes reicht ja. In dem Beispiel eben der Speicher für ein int (plus ABI-Interna).

    Ich denke mal, dass der Compiler zusätzlich ein mögliches "Durchfallen" in den nächsten Zweig entsprechend beachtet.
    Dass die maximal anzunehmende Größe verwendet wird, ist im Grunde auch das Naheliegenste - danke für die superschnelle Klärung 👍 .



  • Stackfrager schrieb:

    D

    rüdiger schrieb:

    Der Stackbedarf des Funktionsscopes + der größte Stackbedarf der Subscopes reicht ja. In dem Beispiel eben der Speicher für ein int (plus ABI-Interna).

    Ich denke mal, dass der Compiler zusätzlich ein mögliches "Durchfallen" in den nächsten Zweig entsprechend beachtet.

    Er muss nur die Scopes beachten. Ob das in einem switch/case ist, ist ja egal.



  • manchmal legen compiler die objekte schon vorher an, wenn sie "wissen", dass es keine seiteneffekte hat (z.b. wenn alle member einfach nur auf 0 gesetzt werden muessen), was aergerlich ist wenn man einen grossen switch block hat, bei dem nur ein bruchteil davon benutzt wird, da durch die initialisierung all der variablen schon viel zeit verstreicht.
    Einziger work around fuer mich war eine externe funktion im case aufzurufen und dafuer zu sorgen, dass sie nicht geinlined wird. leider kostet der call dann auch etwas.



  • rapso schrieb:

    manchmal legen compiler die objekte schon vorher an, wenn sie "wissen", dass es keine seiteneffekte hat (z.b. wenn alle member einfach nur auf 0 gesetzt werden muessen), was aergerlich ist wenn man einen grossen switch block hat, bei dem nur ein bruchteil davon benutzt wird, da durch die initialisierung all der variablen schon viel zeit verstreicht.
    Einziger work around fuer mich war eine externe funktion im case aufzurufen und dafuer zu sorgen, dass sie nicht geinlined wird. leider kostet der call dann auch etwas.

    Ich habe mal gehört, dass Compiler über die Möglichkeit verfügen einzelne Optimierungen gezielt auszuschalten.



  • Um deutlich zu machen: ein Konstruktor wird erst dann aufgerufen, wenn er benötigt wird. Habe ich einen Scope, der nicht durchlaufen wird, wird auch kein Konstruktor aufgerufen. Da gibt es nichts zu deuten. Das heißt auch nicht, dass der Konstruktor verzögert aufgerufen wird. Er wird einfach dann aufgerufen, wenn die Variable angelegt wird.

    Ein durch fallen in den nächsten case ist hier nicht relevant, da in einem case keine Variablen ohne eine Scope-Klammer definiert werden dürfen.

    Mit dem Stack ist das anders. Der wird gleich am Anfang alloziiert. Aber wie einer schon gesagt hat - das ist bloß eine Addition.



  • ich bins schrieb:

    Um deutlich zu machen: ein Konstruktor wird erst dann aufgerufen, wenn er benötigt wird. Habe ich einen Scope, der nicht durchlaufen wird, wird auch kein Konstruktor aufgerufen. Da gibt es nichts zu deuten. Das heißt auch nicht, dass der Konstruktor verzögert aufgerufen wird. Er wird einfach dann aufgerufen, wenn die Variable angelegt wird.

    Grundsätzlich ja.
    Der Compiler kann aber Code generieren der bestimmte Dinge macht, obwohl das Programm den entsprechenden Zweig gar nicht ausführt. Nämlich dann, wenn die Folgen nicht beobachtbar sind.

    Also z.B. in genau so einem Fall wie rapso geschildert hat.

    Relevant ist das aber nur dann, wenn es um Geschwindigkeit geht, da das beobachtbare Verhalten des Programms sich dadurch ja per Definition nicht verändern kann (darf).


Anmelden zum Antworten