Return struct oder pointer zum return struct?



  • Die alloca Lösung kann doch gar nicht funktionieren.
    Der Speicher und damit der Zeiger wird doch sofort ungültig, wenn die myfunc aufgerufen wird.



  • Martin Richter schrieb:

    Die alloca Lösung kann doch gar nicht funktionieren.
    Der Speicher und damit der Zeiger wird doch sofort ungültig, wenn die myfunc aufgerufen wird.

    Nicht beim zurückgehen. Wenn mann dann sofort dereferenziert sollte es okay sein.



  • lk schrieb:

    Nicht beim zurückgehen. Wenn mann dann sofort dereferenziert sollte es okay sein.

    Internet schrieb:

    The alloca() function allocates space in the stack frame of the caller, and returns a pointer to the allocated block. This temporary space is automatically freed when the function from which alloca() is called returns.

    Was gibt es da nicht zu verstehen? Dein gewünschtes Verhalten wird einfach nicht garantiert, da hilft auch kein "aber wenn ich sofort dereferenziere?".
    Gib einfach das struct zurück. Schneller als die aktuelle Lösung mit new dürfte das allemal sein.



  • lk schrieb:

    Martin Richter schrieb:

    Die alloca Lösung kann doch gar nicht funktionieren.
    Der Speicher und damit der Zeiger wird doch sofort ungültig, wenn die myfunc aufgerufen wird.

    Nicht beim zurückgehen. Wenn mann dann sofort dereferenziert sollte es okay sein.

    Nieimleben. Das OS braucht nur Deinem Kontext zu unterbrechen und nur eine Adresse auf dem Stack abzulegen und schon ist es vorbei..





  • wxSkip schrieb:

    cpp-next.com/archive/2009/08/want-speed-pass-by-value/

    Guter Artikel, finde ich hier aber nicht passend.



  • Soll der Aufrufer doch einen Pointer auf ein struct übergeben, das du ihm dann ausfüllst, so wie man das normalerweise macht eben...



  • dot schrieb:

    Soll der Aufrufer doch einen Pointer auf ein struct übergeben, das du ihm dann ausfüllst, so wie man das normalerweise macht eben...

    Und genau das passiert auch normalerweise, wenn man by-value zurückgibt. Der Compiler organisiert das so (schau mal nach RVO), dass du meistens direkt in das Resultat reinschreibst. Daher auch der Artikel. Oder habe ich hier etwas missverstanden?



  • wxSkip schrieb:

    dot schrieb:

    Soll der Aufrufer doch einen Pointer auf ein struct übergeben, das du ihm dann ausfüllst, so wie man das normalerweise macht eben...

    Und genau das passiert auch normalerweise, wenn man by-value zurückgibt. Der Compiler organisiert das so (schau mal nach RVO), dass du meistens direkt in das Resultat reinschreibst. Daher auch der Artikel. Oder habe ich hier etwas missverstanden?

    Die meisten Compiler werden RVO aber nur unter bestimmten Voraussetzungen anwenden...



  • dot schrieb:

    wxSkip schrieb:

    dot schrieb:

    Soll der Aufrufer doch einen Pointer auf ein struct übergeben, das du ihm dann ausfüllst, so wie man das normalerweise macht eben...

    Und genau das passiert auch normalerweise, wenn man by-value zurückgibt. Der Compiler organisiert das so (schau mal nach RVO), dass du meistens direkt in das Resultat reinschreibst. Daher auch der Artikel. Oder habe ich hier etwas missverstanden?

    Die meisten Compiler werden RVO aber nur unter bestimmten Voraussetzungen anwenden...

    Ein Zurückgegebenes Struct > 8 Byte wird bei cdecl immer auf dem Stack in einem Speicher gespeichert werden, den der Aufrufer übergibt. Abhängig vom Funktionsaufbau kann der Compiler RVO oder NRVO anwenden. Das wird aber, wenn der Compiler kein (N)RVO anwenden kann, immer noch mindestens so schnell sein wie Selbst-(N)RVO per Pointer, weil die Bedingungen für den Programmierer die gleichen sind wie für den Compiler.



  • Exakt. Der Compiler kann RVO oder NRVO anwenden. Muss er aber nicht. Spätestens wenn die Methode verschiedene Kontrollpfade hat und/oder verschiedene Objekte zurückgegeben werden, werden viele Compiler afaik die Optimierung nicht durchführen. Warum der Compiler, wenn er die Optimierung nicht durchführt, trotzdem gleich schnell sein soll wie der Programmierer, wenn er händisch optimiert, erschließt sich mir nicht. Nach der Logik würde RVO doch von vornherein keinen Sinn machen!?



  • dot schrieb:

    Exakt. Der Compiler kann RVO oder NRVO anwenden. Muss er aber nicht. Spätestens wenn die Methode verschiedene Kontrollpfade hat und/oder verschiedene Objekte zurückgegeben werden, werden viele Compiler afaik die Optimierung nicht durchführen. Warum der Compiler, wenn er die Optimierung nicht durchführt, gleich schnell sein soll wie der Programmierer, wenn er händisch optimiert, erschließt sich mir nicht. Nach der Logik würde RVO doch von vornherein keinen Sinn machen!?

    Mit einigem Aufwand könnte man sich dem Compiler gegenüber eine Kopie sparen. Aber das lohnt sich gegenüber einem new erst bei großen Speichermengen. Und bei den Angaben des TE sehe ich weder genügend große Speichermengen noch weitläufige Kontrollpfade. Oftmals kann man dem Compiler beim Optimieren vermutlich helfen, indem man ein zurückzugebendes Objekt für mehrere nicht gleichzeitig auftauchende Returnmöglichkeiten verwendet.



  • Man könnte aber auch einfach einen Pointer übergeben, wenn man meint dies tun zu müssen und fertig. Wieso auf einmal new ins Spiel kommt würd mich auch interessieren. Weder der Programmierer noch der Compiler brauchen da irgendwo dynamischen Speicher. Außerdem ging ich irgendwie davon aus, dass wir es hier eher mit C als mit C++ zu tun haben...



  • dot schrieb:

    Man könnte aber auch einfach einen Pointer übergeben, wenn man meint dies tun zu müssen und fertig. Wieso auf einmal new ins Spiel kommt würd mich auch interessieren. Weder der Programmierer noch der Compiler brauchen da irgendwo dynamischen Speicher. Außerdem ging ich irgendwie davon aus, dass wir es hier eher mit C als mit C++ zu tun haben...

    new kommt hier ins Spiel, wenn man eine neue Variable anlegen muss und sie per Pointer zurückgeben muss, denn wie schon erörtert ist alloca() überhaupt nicht standardkonform.



  • bla my_return_struct;
    foo(&bla);
    

    😕



  • dot schrieb:

    bla my_return_struct;
    foo(&bla);
    

    😕

    So, und genau das wird von allen gängigen Compilern genau so optimiert. Eventuell sogar noch ohne Defaultkonstruktoraufruf.

    Was ich meine, was vom Programmierer besser optimiert werden könnte als vom Compiler ist etwa folgendes:

    struct abc
    {
        bool what;
        int very_big_array[10000];
    };
    
    abc *foo1(abc *a1, abc *a2)
    {
        ret = a1->what ? a1 : a2;
    }
    
    abc foo2(abc const &a1, abc const &a2)
    {
        return a1.what ? a1 : a2;
    }
    
    int main1()
    {
        abc a1, a2;
        //Initialisierung
        abc *ret = foo1(&a1, &a2); //keine Kopie
        //Array von ret ausgeben
    }
    
    int main2()
    {
        abc a1, a2;
        //Initialisierung
        abc ret = foo2(a1, a2);  //eine Kopie üblicherweise
        //Array von ret ausgeben
    }
    

    Ein anderer Fall wäre, dass die beiden foo()-Varianten if-verzweigt sind und zwei Variablen b1 und b2 enthält. Im einen Zweig wird b1 zurückgegeben, im anderen Zweig b2. Nun musst du 2 Variablen erstellen, der Compiler kann die Rückgabekopie auch per NRVO nicht optimieren. Mit deiner Variante passiert exakt das Gleiche wie beim Compiler ohne Optimierung: Konstruktion von b1 und b2, Kopie in den Zielspeicher. Wird dagegen die Variable innerhalb von foo1() per new alloziert und einfach der Pointer zurückgegeben, entfällt die Rückgabekopie. Dafür hast du dann halt den Allokationsaufwand.



  • wxSkip schrieb:

    So, und genau das wird von allen gängigen Compilern genau so optimiert.

    Wie du selbst sagst aber eben nicht immer. Wenn es sich nicht gerade um triviale Fälle handelt, kann sich also kaum drauf verlassen. Nicht mehr und nicht weniger hab ich je gesagt. Wenn es also performancekritisch ist, dann kann man es auch einfach selbst machen. So Wahnsinnig kompliziert isses ja jetzt nicht gerade. Was den Defaultkonstruktor angeht: Ich ging wie gesagt davon aus, dass es dem Threadersteller um C geht, was natürlich POD impliziert. In C++ wird man sowas generell wohl eher selten machen.



  • dot schrieb:

    wxSkip schrieb:

    So, und genau das wird von allen gängigen Compilern genau so optimiert.

    Wie du selbst sagst aber eben nicht immer. Wenn es sich nicht gerade um triviale Fälle handelt, kann sich also kaum drauf verlassen. Nicht mehr und nicht weniger hab ich je gesagt. Wenn es also performancekritisch ist, dann kann man es auch einfach selbst machen. So Wahnsinnig kompliziert isses ja jetzt nicht gerade. Was den Defaultkonstruktor angeht: Ich ging wie gesagt davon aus, dass es dem Threadersteller um C geht, was natürlich POD impliziert. In C++ wird man sowas generell wohl eher selten machen.

    Doch. Das Ergebnis wird (praktisch) immer vor dem Zurückgeben in das Zielobjekt geschrieben. Die Frage ist bloß, ob gleich eine Variable, die immer zurückgegeben wird, direkt mit dem Zielspeicher identisch ist, oder ob die Rückgabevariable eben nicht direkt im Zielspeicher erstellt werden kann und erst dort hinein kopiert werden muss. Daran ändert auch dein Code nichts. Bei meinem Code gibt es hingegen gar keine Zielvariable, sondern nur einen Pointer darauf.


  • Mod

    Praktisch jede vernünftig geschriebene Funktion lässt sich in eine Form bringen, mit der NRVO trivial anzuwenden ist. RVO ist sowieso trivial.

    dot schrieb:

    So Wahnsinnig kompliziert isses ja jetzt nicht gerade.



  • @wxSkip: Dir ist schon klar, daß die erste Variante gar nichts macht? foo1() ändert den (lokalen) Parameter und hat keine Auswirkungen auf das ausrufende Programm.


Anmelden zum Antworten