it-swarm.com.de

Ist es sicher, eine Struktur in C oder C ++ zurückzugeben?

Ich verstehe, dass dies nicht getan werden sollte, aber ich glaube, ich habe Beispiele gesehen, die so etwas tun (Hinweiscode 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 malloced-Struktur zurückgeben sollten, wenn wir so etwas tun möchten, 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 eine Übergabe unter Bezugnahme auf die Funktion und ändere die Werte dort. (Nach meinem Verständnis kann der Stapel, der zur Zuweisung der Struktur verwendet wurde, überschrieben werden, sobald der Funktionsumfang abgelaufen ist.).

Fügen wir der Frage einen zweiten Teil hinzu: Variiert dies je nach Compiler? Wenn ja, wie verhalten sich die neuesten Versionen von Compilern für Desktops: gcc, g ++ und Visual Studio?

Gedanken dazu?

78
jzepeda

Es ist absolut sicher und es ist nicht falsch, dies zu tun. Auch: es variiert nicht je nach Compiler.

Normalerweise, wenn (wie in Ihrem Beispiel) Ihre Struktur nicht zu groß ist, würde ich argumentieren, dass dieser Ansatz sogar besser ist als die Rückgabe einer mallocierten Struktur (malloc ist eine teure Operation).

72

Es ist absolut sicher.

Sie kehren wertmäßig zurück. Was zu undefiniertem Verhalten führen würde, ist, wenn Sie als Referenz zurückkehren würden.

//safe
mystruct func(int c, int d){
    mystruct retval;
    retval.a = c;
    retval.b = d;
    return retval;
}

//undefined behavior
mystruct& func(int c, int d){
    mystruct retval;
    retval.a = c;
    retval.b = d;
    return retval;
}

Das Verhalten Ihres Snippets ist absolut gültig und definiert. Es variiert nicht je nach Compiler. Ist schon ok!

Persönlich gebe ich immer entweder einen Zeiger auf eine malloced Struktur zurück

Das solltest du nicht. Sie sollten dynamisch zugewiesenen Speicher nach Möglichkeit vermeiden.

oder machen Sie einfach einen Pass-by-Verweis auf die Funktion und ändern Sie die Werte dort.

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.

Nach meinem Verständnis kann der Stapel, der zur Zuweisung der Struktur verwendet wurde, überschrieben werden, sobald der Funktionsumfang abgelaufen ist

Das ist falsch. Ich meinte, es ist irgendwie korrekt, aber Sie geben eine Kopie der Struktur zurück, die Sie in der Funktion erstellen. Theoretisch. In der Praxis kann und wird RVO auftreten. Informieren Sie sich über die Rückgabewert-Optimierung. Dies bedeutet, dass retval nach dem Beenden der Funktion anscheinend außerhalb des Gültigkeitsbereichs liegt, jedoch möglicherweise tatsächlich im aufrufenden Kontext erstellt wird, um die zusätzliche Kopie zu verhindern. Dies ist eine Optimierung, die der Compiler frei implementieren kann.

68
Luchian Grigore

Die Lebensdauer des Objekts mystruct 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.

9

Es ist vollkommen legal, aber bei großen Strukturen müssen zwei Faktoren berücksichtigt werden: Geschwindigkeit und Stapelgröße.

5
ebutusov

Ein Strukturtyp kann der Typ für den von einer Funktion zurückgegebenen Wert sein. Es ist sicher, da der Compiler eine Kopie von struct erstellt und die Kopie zurückgibt, nicht die lokale Struktur in der Funktion.

typedef struct{
    int a,b;
}mystruct;

mystruct func(int c, int d){
    mystruct retval;
    cout << "func:" <<&retval<< endl;
    retval.a = c;
    retval.b = d;
    return retval;
}

int main()
{
    cout << "main:" <<&(func(1,2))<< endl;


    system("pause");
}
4
haberdar

Es ist absolut sicher, eine Struktur so zurückzugeben, wie Sie es getan haben.

Basierend auf dieser Aussage jedoch: Da ich verstehe, dass, sobald der Umfang der Funktion abgelaufen ist, jeder Stapel, der zur Zuweisung der Struktur verwendet wurde, überschrieben werden kann Ich würde mir nur ein Szenario vorstellen, in dem eines der Mitglieder vorhanden ist der Struktur wurde dynamisch zugewiesen (malloced oder new'ed). In diesem Fall werden die dynamisch zugewiesenen Mitglieder ohne RVO zerstört, und auf der zurückgegebenen Kopie befindet sich ein Mitglied, das auf Müll zeigt.

4
maress

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 Wertes einige Operationen aus (unter anderem):

  1. Ruft den Kopierkonstruktor mystruct(const mystruct&) auf (this ist eine temporäre Variable außerhalb die vom Compiler selbst zugewiesene Funktion func)
  2. ruft den Destruktor ~mystruct für die Variable auf, die in func zugewiesen wurde.
  3. ruft mystruct::operator= auf, wenn der zurückgegebene Wert mit = einem anderen Wert zugewiesen wurde
  4. ruft den Destruktor ~mystruct für die vom Compiler verwendete temporäre Variable auf

Wenn mystruct so einfach ist wie das hier beschriebene, ist alles in Ordnung, aber wenn es einen Zeiger (wie char*) Oder eine kompliziertere Speicherverwaltung hat, hängt alles davon ab, wie mystruct::operator=, mystruct(const mystruct&) und ~mystruct sind implementiert. Daher empfehle ich Vorsichtsmaßnahmen, wenn komplexe Datenstrukturen als Wert zurückgegeben werden.

4
Filippo

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 eine andere Weise abgerufen werden sollten.

Nachfolgend finden Sie ein einfaches Beispiel zur Bestätigung (entnommen aus der Mingw-Compiler-Assembly).

_func:
    Push    ebp
    mov ebp, esp
    sub esp, 16
    mov eax, DWORD PTR [ebp+8]
    mov DWORD PTR [ebp-8], eax
    mov eax, DWORD PTR [ebp+12]
    mov DWORD PTR [ebp-4], eax
    mov eax, DWORD PTR [ebp-8]
    mov edx, DWORD PTR [ebp-4]
    leave
    ret

Sie können sehen, dass der Wert von b über edx übertragen wurde. während der Standard-EAX einen Wert für a enthält.

3
perilbrain

Fügen wir der Frage einen zweiten Teil hinzu: Variiert dies je nach Compiler?

In der Tat, wie ich zu meinem Schmerz festgestellt habe: http://sourceforge.net/p/mingw-w64/mailman/message/33176880/

Ich habe gcc auf win32 (MinGW) verwendet, um COM-Schnittstellen aufzurufen, die structs zurückgaben. Es stellt sich heraus, dass MS das anders macht als GNU) und so mein (gcc) Programm mit einem zerschmetterten Stack abgestürzt ist.

Es könnte sein, dass MS hier den höheren Rang einnimmt - aber alles, was mich interessiert, ist die ABI-Kompatibilität zwischen MS und GNU für das Bauen auf Windows.

Wenn dies der Fall ist, wie verhalten sich die neuesten Versionen von Compilern für Desktops: gcc, g ++ und Visual Studio

Auf einer Wine-Mailingliste finden Sie einige Nachrichten darüber, wie MS dies zu tun scheint.

2
effbiae

Es ist nicht sicher, eine Struktur zurückzugeben. Ich liebe es, es selbst zu tun, aber wenn später jemand 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. Also, auf Ihre Kosten, ist mein Tipp kurz.

2
Igor Polk