Was ich verstehe ist, dass dies nicht getan werden sollte, aber ich glaube, ich habe Beispiele gesehen, die so etwas tun (Notizcode ist nicht unbedingt syntaktisch korrekt, aber die Idee ist da)
typedef struct{
int a,b;
}mystruct;
Und dann ist hier eine Funktion
mystruct func(int c, int d){
mystruct retval;
retval.a = c;
retval.b = d;
return retval;
}
Ich habe verstanden, dass wir immer einen Zeiger auf eine malloc'ed Struktur zurückgeben sollten, wenn wir so etwas tun wollen, aber ich bin mir sicher, dass ich Beispiele gesehen habe, die so etwas tun. Ist das richtig? Persönlich gebe ich immer entweder einen Zeiger auf eine malloc'ed Struktur zurück oder mache einfach einen Durchlauf unter Bezugnahme auf die Funktion und ändere die Werte dort. (Da ich verstehe, dass nach Ablauf des Funktionsumfangs jeder Stapel, der zum Zuweisen der Struktur verwendet wurde, überschrieben werden kann).
Fügen wir der Frage einen zweiten Teil hinzu: Variiert dies je nach Compiler? Wenn ja, wie verhalten sich dann die neuesten Versionen von Compilern für Desktops: gcc, g ++ und Visual Studio?
Gedanken dazu?
Antworten:
Es ist absolut sicher und es ist nicht falsch, dies zu tun. Außerdem: Es variiert nicht je nach Compiler.
Wenn (wie in Ihrem Beispiel) Ihre Struktur nicht zu groß ist, würde ich normalerweise argumentieren, dass dieser Ansatz sogar besser ist als die Rückgabe einer malloc'ed Struktur (
malloc
ist eine teure Operation).quelle
Es ist absolut sicher.
Sie kehren nach Wert zurück. Was zu undefiniertem Verhalten führen würde, wäre, wenn Sie als Referenz zurückkehren würden.
Das Verhalten Ihres Snippets ist vollkommen gültig und definiert. Es variiert nicht je nach Compiler. Es ist in Ordnung!
Das solltest du nicht. Sie sollten dynamisch zugewiesenen Speicher nach Möglichkeit vermeiden.
Diese Option ist vollkommen gültig. Es ist eine Frage der Wahl. Im Allgemeinen tun Sie dies, wenn Sie etwas anderes von der Funktion zurückgeben möchten, während Sie die ursprüngliche Struktur ändern.
Das ist falsch. Ich meinte, es ist irgendwie korrekt, aber Sie geben eine Kopie der Struktur zurück, die Sie innerhalb der Funktion erstellt haben. Theoretisch . In der Praxis kann und wird RVO auftreten. Informieren Sie sich über die Optimierung des Rückgabewerts. Dies bedeutet, dass
retval
der Funktionsumfang nach Beendigung der Funktion möglicherweise nicht mehr im aufrufenden Kontext erstellt wird, um die zusätzliche Kopie zu verhindern. Dies ist eine Optimierung, die der Compiler frei implementieren kann.quelle
Die Lebensdauer des
mystruct
Objekts in Ihrer Funktion endet tatsächlich, wenn Sie die Funktion verlassen. Sie übergeben das Objekt jedoch als Wert in der return-Anweisung. Dies bedeutet, dass das Objekt aus der Funktion in die aufrufende Funktion kopiert wird. Das ursprüngliche Objekt ist verschwunden, aber die Kopie lebt weiter.quelle
Es ist nicht nur sicher, ein
struct
in C zurückzugeben (oder einclass
in C ++, wostruct
-s tatsächlichclass
-es mit Standardmitgliedernpublic:
sind), sondern eine Menge Software tut dies auch.Natürlich
class
gibt die Sprache bei der Rückgabe eines in C ++ an, dass ein Destruktor oder ein sich bewegender Konstruktor aufgerufen wird, aber es gibt viele Fälle, in denen dies vom Compiler optimiert werden könnte.Darüber hinaus gibt der Linux x86-64 ABI an, dass die Rückgabe von a
struct
mit zwei skalaren (z. B. Zeigern oderlong
) Werten über die Register (%rax
&%rdx
) erfolgt, was sehr schnell und effizient ist. In diesem speziellen Fall ist es wahrscheinlich schneller, solche Felder mit zwei Skalaren zurückzugeben,struct
als irgendetwas anderes zu tun (z. B. sie in einem als Argument übergebenen Zeiger zu speichern).Das Zurückgeben eines solchen Zwei-Skalar-Feldes
struct
ist dann viel schneller als dasmalloc
Zurückgeben und das Zurückgeben eines Zeigers.quelle
Es ist vollkommen legal, aber bei großen Strukturen müssen zwei Faktoren berücksichtigt werden: Geschwindigkeit und Stapelgröße.
quelle
Ein Strukturtyp kann der Typ für den von einer Funktion zurückgegebenen Wert sein. Dies ist sicher, da der Compiler eine Kopie von struct erstellt und die Kopie zurückgibt, nicht die lokale Struktur in der Funktion.
quelle
Die Sicherheit hängt davon ab, wie die Struktur selbst implementiert wurde. Ich bin gerade auf diese Frage gestoßen, als ich etwas Ähnliches implementiert habe, und hier ist das potenzielle Problem.
Der Compiler führt bei der Rückgabe des Werts einige Operationen aus (unter anderem):
mystruct(const mystruct&)
(this
ist eine temporäre Variable außerhalb derfunc
vom Compiler selbst zugewiesenen Funktion ).~mystruct
für die Variable auf, die darin zugewiesen wurdefunc
mystruct::operator=
wenn der zurückgegebene Wert mit etwas anderem zugewiesen ist=
~mystruct
für die vom Compiler verwendete temporäre Variable aufWenn nun
mystruct
ist so einfach wie das hier alles beschrieben ist in Ordnung, aber wenn es Zeiger (wie hatchar*
) oder kompliziertere Speicherverwaltung, dann es hängt alles davon ab , wiemystruct::operator=
,mystruct(const mystruct&)
und~mystruct
umgesetzt werden. Daher empfehle ich Vorsichtsmaßnahmen, wenn komplexe Datenstrukturen als Wert zurückgegeben werden.quelle
Es ist absolut sicher, eine Struktur so zurückzugeben, wie Sie es getan haben.
Basierend auf dieser Aussage jedoch: Da ich verstehe, dass nach Ablauf des Funktionsumfangs jeder Stapel, der zum Zuweisen der Struktur verwendet wurde, überschrieben werden kann, würde ich mir nur ein Szenario vorstellen, in dem eines der Mitglieder der Struktur dynamisch zugewiesen wurde ( malloc'ed oder new'ed). In diesem Fall werden ohne RVO die dynamisch zugewiesenen Mitglieder zerstört und auf der zurückgegebenen Kopie wird ein Mitglied auf Müll verweisen.
quelle
Ich werde auch sftrabbit zustimmen. Das Leben endet tatsächlich und der Stapelbereich wird aufgeräumt, aber der Compiler ist intelligent genug, um sicherzustellen, dass alle Daten in Registern oder auf andere Weise abgerufen werden.
Ein einfaches Beispiel zur Bestätigung ist unten angegeben (entnommen aus der Mingw-Compiler-Assembly).
Sie können sehen, dass der Wert von b über edx übertragen wurde. während das Standard-eax den Wert für a enthält.
quelle
Es ist nicht sicher, eine Struktur zurückzugeben. Ich mache es gerne selbst, aber wenn jemand später einen Kopierkonstruktor zur zurückgegebenen Struktur hinzufügt, wird der Kopierkonstruktor aufgerufen. Dies kann unerwartet sein und den Code beschädigen. Dieser Fehler ist sehr schwer zu finden.
Ich hatte eine ausführlichere Antwort, aber der Moderator mochte es nicht. Auf Ihre Kosten ist mein Tipp also kurz.
quelle
In der Tat, wie ich zu meinem Schmerz herausgefunden habe: http://sourceforge.net/p/mingw-w64/mailman/message/33176880/
Ich habe gcc unter win32 (MinGW) verwendet, um COM-Schnittstellen aufzurufen, die Strukturen zurückgeben. Es stellt sich heraus, dass MS es anders macht als GNU und so stürzte mein (gcc) Programm mit einem zerschlagenen Stapel ab.
Es könnte sein, dass MS hier einen höheren Stellenwert hat - aber alles, was mich interessiert, ist die ABI-Kompatibilität zwischen MS und GNU für den Aufbau unter Windows.
Auf einer Wine-Mailingliste finden Sie einige Nachrichten darüber, wie MS dies zu tun scheint.
quelle
Hinweis: Diese Antwort gilt nur für C ++ 11 und höher. Es gibt kein "C / C ++", es sind verschiedene Sprachen.
Nein, es besteht keine Gefahr, ein lokales Objekt nach Wert zurückzugeben, und es wird empfohlen, dies zu tun. Ich denke jedoch, dass in allen Antworten hier ein wichtiger Punkt fehlt. Viele andere haben gesagt, dass die Struktur entweder kopiert oder direkt mit RVO platziert wird. Dies ist jedoch nicht ganz richtig. Ich werde versuchen, genau zu erklären, welche Dinge bei der Rückgabe eines lokalen Objekts passieren können.
Semantik verschieben
Seit c ++ 11 haben wir rWertreferenzen, die auf temporäre Objekte verweisen, denen sicher gestohlen werden kann. Beispielsweise verfügt std :: vector über einen Verschiebungskonstruktor sowie einen Verschiebungszuweisungsoperator. Beide haben eine konstante Komplexität und kopieren einfach den Zeiger auf die Daten des Vektors, von dem verschoben wird. Ich werde hier nicht näher auf die Bewegungssemantik eingehen.
Da ein lokal innerhalb einer Funktion erstelltes Objekt temporär ist und bei der Rückkehr der Funktion den Gültigkeitsbereich verlässt, wird ein zurückgegebenes Objekt ab C ++ 11 niemals mehr kopiert. Der Verschiebungskonstruktor wird für das zurückgegebene Objekt aufgerufen (oder nicht, wie später erläutert). Dies bedeutet, dass, wenn Sie ein Objekt mit einem teuren Kopierkonstruktor, aber einem kostengünstigen Verschiebungskonstruktor wie einem großen Vektor zurückgeben, nur das Eigentum an den Daten vom lokalen Objekt auf das zurückgegebene Objekt übertragen wird - was billig ist.
Beachten Sie, dass es in Ihrem speziellen Beispiel keinen Unterschied zwischen dem Kopieren und Verschieben des Objekts gibt. Die Standardkonstruktoren zum Verschieben und Kopieren Ihrer Struktur führen zu denselben Operationen. Kopieren von zwei ganzen Zahlen. Dies ist jedoch mindestens so schnell wie jede andere Lösung, da die gesamte Struktur in ein 64-Bit-CPU-Register passt (korrigieren Sie mich, wenn ich falsch liege, ich kenne nicht viele CPU-Register).
RVO und NRVO
RVO bedeutet Rückgabewertoptimierung und ist eine der wenigen Optimierungen, die Compiler durchführen und die Nebenwirkungen haben können. Seit c ++ 17 ist RVO erforderlich. Wenn ein unbenanntes Objekt zurückgegeben wird, wird es direkt an der Stelle erstellt, an der der Aufrufer den zurückgegebenen Wert zuweist. Weder der Kopierkonstruktor noch der Verschiebungskonstruktor werden aufgerufen. Ohne RVO würde das unbenannte Objekt zuerst lokal erstellt, dann in der zurückgegebenen Adresse erstellt und dann das lokale unbenannte Objekt zerstört.
Beispiel, bei dem RVO erforderlich ist (c ++ 17) oder wahrscheinlich (vor c ++ 17):
NRVO bedeutet Named Return Value Optimization und ist dasselbe wie RVO, außer dass es für ein benanntes Objekt durchgeführt wird, das lokal für die aufgerufene Funktion ist. Dies wird vom Standard (c ++ 20) immer noch nicht garantiert, aber viele Compiler tun dies immer noch. Beachten Sie, dass selbst bei benannten lokalen Objekten diese bei der Rückgabe im schlimmsten Fall verschoben werden.
Fazit
Der einzige Fall, in dem Sie in Betracht ziehen sollten, nicht nach Wert zurückzukehren, ist, wenn Sie ein benanntes, sehr großes Objekt (wie in seiner Stapelgröße) haben. Dies liegt daran, dass NRVO (ab c ++ 20) noch nicht garantiert ist und selbst das Bewegen des Objekts langsam wäre. Meine Empfehlung und die Empfehlung in den Cpp-Kernrichtlinien lautet, Objekte immer nach Wert zurückzugeben (wenn mehrere Rückgabewerte verwendet werden, verwenden Sie struct (oder tuple)), wobei die einzige Ausnahme darin besteht, dass das Verschieben des Objekts teuer ist. Verwenden Sie in diesem Fall einen nicht konstanten Referenzparameter.
Es ist NIE eine gute Idee, eine Ressource zurückzugeben, die manuell von einer Funktion in C ++ freigegeben werden muss. TU das niemals. Verwenden Sie mindestens ein std :: unique_ptr oder erstellen Sie eine eigene nicht lokale oder lokale Struktur mit einem Destruktor, der seine Ressource ( RAII ) freigibt, und geben Sie eine Instanz davon zurück. Es wäre dann auch eine gute Idee, den Verschiebungskonstruktor und den Verschiebungszuweisungsoperator zu definieren, wenn die Ressource keine eigene Verschiebungssemantik hat (und den Kopierkonstruktor / die Zuweisung löschen).
quelle