Return struct oder pointer zum return struct?
-
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.
-
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.
-
camper schrieb:
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.
Was wenn du mehrere Returnpfade hast? Händisch isses auch dann noch trivial, aber MSVC z.B. würde dort afaik zumindest keine NRVO anwenden. Ich sag ja auch gar nix gegen RVO. Wenn man weiß was man tut und sich auf seine(n) Compiler verlassen kann, dann wird man das sinnvollerweise auch tun. Nur is das eben ein Pattern, auf das man in C sowieso ständig irgendwo trifft. Darum hab ich das einfach mal vorgeschlagen, anstatt der äußerst merkwürdigen Vorschläge, die davor so herumschwirrten (
alloca()in der Funktion z.B.
). Ich möchte nur verhindern, dass mancher jetzt vielleicht glaubt, dass man am besten einfach ohne nachdenken alles als Wert returned und davon ausgeht, dass der Compiler es schon richten wird.
-
CStoll schrieb:
@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.
Ups, wurde korrigiert.
-
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?
Hmja.
OK. Wenn man ein wenig weiter denkt als die ursprüngliche Frage des OP, dann könnte es passen
Dann könnte die Funktion nämlich u.U. so aussehen:
ppackage myfunc(ppackage pack);Dann haben wir genau den Fall der in dem Artikel angesprochen wird.
Macht allerdings nur Sinn wenn entweder- bei den meisten Aufrufen von myfunc() auch wirklich was zurückgegeben wird oder
- das Paket mit dem myfunc() aufgerufen wird sehr oft ein Temporary ist bzw. "gemoved" werden kann