Gibt eine Struktur von einer Funktion in C zurück

171

Heute habe ich ein paar Freunden beigebracht, wie man C structs benutzt. Einer von ihnen fragte, ob Sie structeine Funktion von einer Funktion zurückgeben könnten , auf die ich antwortete: "Nein! Sie würden stattdessen Zeiger auf dynamisch malloced structs zurückgeben."

Ich kam von jemandem, der hauptsächlich C ++ macht, und hatte erwartet, dass er structs nicht nach Werten zurückgeben kann. In C ++ können Sie die operator =für Ihre Objekte überladen und es ist absolut sinnvoll, eine Funktion zu haben, um Ihr Objekt nach Wert zurückzugeben. In C haben Sie diese Option jedoch nicht und ich habe darüber nachgedacht, was der Compiler tatsächlich tut. Folgendes berücksichtigen:

struct MyObj{
    double x, y;
};

struct MyObj foo(){
    struct MyObj a;

    a.x = 10;
    a.y = 10;

    return a;
}        

int main () {

    struct MyObj a;

    a = foo();    // This DOES work
    struct b = a; // This does not work

    return 0;
}    

Ich verstehe, warum struct b = a;sollte nicht funktionieren - Sie können nicht operator =für Ihren Datentyp überladen . Wie ist es, dass gut a = foo();kompiliert? Bedeutet es etwas anderes als struct b = a;? Vielleicht lautet die Frage: Was genau macht die returnAussage in Verbindung mit dem =Unterschreiben?

[bearbeiten]: Ok, ich wurde nur darauf hingewiesen, dass struct b = aes sich um einen Syntaxfehler handelt - das ist richtig und ich bin ein Idiot! Das macht es aber noch komplizierter! Verwenden struct MyObj b = afunktioniert in der Tat! Was fehlt mir hier?

mmirzadeh
quelle
23
struct b = a;ist ein Syntaxfehler. Was ist, wenn Sie es versuchen struct MyObj b = a;?
Greg Hewgill
2
@ GregHewgill: Du hast absolut recht. Interessanterweise struct MyObj b = a;scheint es jedoch zu funktionieren :)
mmirzadeh

Antworten:

199

Sie können =problemlos eine Struktur aus einer Funktion zurückgeben (oder den Operator verwenden). Es ist ein klar definierter Teil der Sprache. Das einzige Problem dabei struct b = aist, dass Sie keinen vollständigen Typ angegeben haben. struct MyObj b = awird gut funktionieren. Sie können Strukturen auch an Funktionen übergeben - eine Struktur ist genau die gleiche wie jeder integrierte Typ, um Parameter zu übergeben, Werte zurückzugeben und zuzuweisen.

Hier ist ein einfaches Demonstrationsprogramm, das alle drei Aufgaben ausführt: Übergibt eine Struktur als Parameter, gibt eine Struktur aus einer Funktion zurück und verwendet Strukturen in Zuweisungsanweisungen:

#include <stdio.h>

struct a {
   int i;
};

struct a f(struct a x)
{
   struct a r = x;
   return r;
}

int main(void)
{
   struct a x = { 12 };
   struct a y = f(x);
   printf("%d\n", y.i);
   return 0;
}

Das nächste Beispiel ist ziemlich genau das gleiche, verwendet jedoch den eingebauten intTyp zu Demonstrationszwecken. Die beiden Programme haben das gleiche Verhalten in Bezug auf die Wertübergabe für die Parameterübergabe, Zuweisung usw.:

#include <stdio.h>

int f(int x) 
{
  int r = x;
  return r;
}

int main(void)
{
  int x = 12;
  int y = f(x);
  printf("%d\n", y);
  return 0;
}
Carl Norum
quelle
14
Das ist ziemlich interessant. Ich hatte immer den Eindruck, dass man dafür Hinweise braucht. Ich habe mich geirrt :)
mmirzadeh
8
Sie brauchen sicherlich keine Zeiger. Das heißt, die meiste Zeit würden Sie sie verwenden wollen - die impliziten Speicherkopien, die stattfinden, um Strukturen nach Wert zu schleudern, können eine echte Verschwendung von CPU-Zyklen sein, ganz zu schweigen von der Speicherbandbreite.
Carl Norum
10
@CarlNorum Wie groß muss eine Struktur sein, damit eine Kopie mehr kostet als malloc + kostenlos?
Josefx
7
@ Josefx, eine einzelne Kopie? Wahrscheinlich riesig. Die Sache ist, normalerweise, wenn Sie Strukturen nach Wert weitergeben, kopieren Sie sie oft . Jedenfalls ist es nicht so einfach. Sie könnten lokale oder globale Strukturen weitergeben. In diesem Fall sind Ihre Zuweisungskosten so gut wie kostenlos.
Carl Norum
7
Sie benötigen Zeiger und Speicherzuweisung für den zurückgegebenen Wert außerhalb des Funktionskörpers, sobald die für einen Wert zugewiesene Speichermenge zur Kompilierungszeit nicht bekannt ist. Da es sich um Strukturen handelt, können C-Funktionen diese problemlos zurückgeben.
Reinierpost
33

Wenn Sie einen Aufruf wie z. B. a = foo();ausführen, überträgt der Compiler möglicherweise die Adresse der Ergebnisstruktur auf den Stapel und übergibt sie als "versteckten" Zeiger an die foo()Funktion. Tatsächlich könnte es so etwas wie:

void foo(MyObj *r) {
    struct MyObj a;
    // ...
    *r = a;
}

foo(&a);

Die genaue Implementierung hängt jedoch vom Compiler und / oder der Plattform ab. Wie Carl Norum bemerkt, kann die Struktur, wenn sie klein genug ist, sogar vollständig in einem Register zurückgegeben werden.

Greg Hewgill
quelle
11
Das ist völlig implementierungsabhängig. Beispielsweise übergibt armcc ausreichend kleine Strukturen in den regulären Registern für die Parameterübergabe (oder den Rückgabewert).
Carl Norum
Würde das nicht einen Zeiger auf eine lokale Variable zurückgeben? Der Speicher für die zurückgegebene Struktur kann nicht Teil des fooStapelrahmens sein. Es muss an einem Ort sein, der nach der Rückkehr von überlebt foo.
Anders Abel
@AndersAbel: Ich denke , das meint , was Greg ist , dass Compiler einen Zeiger auf die Variable in der nimmt Hauptfunktion und übergibt sie an die Funktion foo. Innerhalb der Funktion fooerledigen Sie einfach die Aufgabe
mmirzadeh
4
@AndersAbel: Das *r = aam Ende würde (effektiv) eine Kopie der lokalen Variablen in die Variable des Aufrufers erstellen . Ich sage "effektiv", weil der Compiler RVO implementieren und die lokale Variable avollständig entfernen könnte .
Greg Hewgill
3
Obwohl dies die Frage nicht direkt beantwortet, ist dies der Grund, warum viele Leute über Google hierher fallen c return struct: Sie wissen, dass in cdecl der eaxWert zurückgegeben wird und dass Strukturen im Allgemeinen nicht hineinpassen eax. Das habe ich gesucht.
Ciro Santilli 22 冠状 病 六四 事件 22
14

Die struct bZeile funktioniert nicht, da es sich um einen Syntaxfehler handelt. Wenn Sie es um den Typ erweitern, funktioniert es einwandfrei

struct MyObj b = a;  // Runs fine

Was C hier tut, ist im Wesentlichen eine memcpyvon der Quellstruktur zum Ziel. Dies gilt sowohl für die Zuweisung als auch für die Rückgabe von structWerten (und wirklich für jeden anderen Wert in C).

JaredPar
quelle
+1, tatsächlich geben viele Compiler memcpyin diesem Fall tatsächlich einen wörtlichen Aufruf an - zumindest wenn die Struktur einigermaßen groß ist.
Carl Norum
Also, während der Initialisierung eines Datentyps funktioniert die memcpy-Funktion?
Bhuwansahni
1
@bhuwansahni Ich bin mir nicht ganz sicher, was du hier fragst. Könnten Sie etwas näher darauf eingehen?
JaredPar
4
@JaredPar - Compiler rufen die memcpyFunktion für die Struktursituationen häufig buchstäblich auf . Sie können ein schnelles Testprogramm erstellen und beispielsweise von GCC ausführen lassen. Für integrierte Typen, die nicht vorkommen - sie sind nicht groß genug, um diese Art der Optimierung auszulösen.
Carl Norum
3
Es ist definitiv möglich, dies zu verwirklichen. Für das Projekt, an dem ich arbeite, ist kein memcpySymbol definiert. Daher treten häufig Linkerfehler mit "undefinierten Symbolen" auf, wenn der Compiler beschließt, eines selbst auszuspucken.
Carl Norum
9

Ja, es ist möglich, dass wir auch die Struktur übergeben und die Struktur zurückgeben können. Sie hatten Recht, aber Sie haben den Datentyp, der wie diese Struktur aussehen sollte, tatsächlich nicht übergeben. MyObj b = a.

Eigentlich habe ich auch erfahren, als ich versucht habe, eine bessere Lösung zu finden, um mehr als einen Wert für die Funktion ohne Verwendung eines Zeigers oder einer globalen Variablen zurückzugeben.

Im Folgenden finden Sie ein Beispiel dafür, in dem die Abweichung der Durchschnittsnoten eines Schülers berechnet wird.

#include<stdio.h>
struct marks{
    int maths;
    int physics;
    int chem;
};

struct marks deviation(struct marks student1 , struct marks student2 );

int main(){

    struct marks student;
    student.maths= 87;
    student.chem = 67;
    student.physics=96;

    struct marks avg;
    avg.maths= 55;
    avg.chem = 45;
    avg.physics=34;
    //struct marks dev;
    struct marks dev= deviation(student, avg );
    printf("%d %d %d" ,dev.maths,dev.chem,dev.physics);

    return 0;
 }

struct marks deviation(struct marks student , struct marks student2 ){
    struct marks dev;

    dev.maths = student.maths-student2.maths;
    dev.chem = student.chem-student2.chem;
    dev.physics = student.physics-student2.physics; 

    return dev;
}
Ein Mann
quelle
5

Soweit ich mich erinnern kann, durften die ersten Versionen von C nur einen Wert zurückgeben, der in ein Prozessorregister passen könnte, was bedeutet, dass Sie nur einen Zeiger auf eine Struktur zurückgeben konnten. Die gleiche Einschränkung gilt für Funktionsargumente.

Neuere Versionen ermöglichen die Weitergabe größerer Datenobjekte wie Strukturen. Ich denke, diese Funktion war bereits in den achtziger oder frühen neunziger Jahren üblich.

Arrays können jedoch weiterhin nur als Zeiger übergeben und zurückgegeben werden.

Giorgio
quelle
Sie können ein Array nach Wert zurückgeben, wenn Sie es in eine Struktur einfügen. Was Sie nicht als Wert zurückgeben können, ist ein Array mit variabler Länge.
Han
1
Ja, ich kann ein Array in eine Struktur einfügen, aber ich kann nicht zB typedef char arr [100] schreiben; arr foo () {...} Ein Array kann nicht zurückgegeben werden, auch wenn die Größe bekannt ist.
Giorgio
Könnte der Downvoter den Grund für die Downvote erklären? Wenn meine Antwort falsche Informationen enthält, würde ich diese gerne korrigieren.
Giorgio
4

Sie können Strukturen in C zuweisen. Dies a = b; ist eine gültige Syntax.

Sie haben einfach einen Teil des Typs - das struct-Tag - in Ihrer Zeile weggelassen, der nicht funktioniert.

DigitalRoss
quelle
4

Es ist kein Problem, eine Struktur zurückzugeben. Es wird als Wert übergeben

Was aber, wenn die Struktur ein Mitglied enthält, das die Adresse einer lokalen Variablen hat?

struct emp {
    int id;
    char *name;
};

struct emp get() {
    char *name = "John";

    struct emp e1 = {100, name};

    return (e1);
}

int main() {

    struct emp e2 = get();

    printf("%s\n", e2.name);
}

Hier enthält e1.name eine Speicheradresse, die lokal für die Funktion get () ist. Sobald get () zurückkehrt, wäre die lokale Adresse für den Namen freigegeben worden. Wenn wir im Anrufer versuchen, auf diese Adresse zuzugreifen, kann dies zu einem Segmentierungsfehler führen, da wir versuchen, eine freigegebene Adresse zu verwenden. Das ist schlecht..

Wobei die e1.id vollkommen gültig ist, da ihr Wert nach e2.id kopiert wird

Wir sollten daher immer versuchen, die Rückgabe lokaler Speicheradressen einer Funktion zu vermeiden.

Alles, was malloced ist, kann nach Bedarf zurückgegeben werden

Jagan
quelle
2
struct emp {
    int id;
    char *name;
};

struct emp get() {
    char *name = "John";

    struct emp e1 = {100, name};

    return (e1);
}

int main() {

    struct emp e2 = get();

    printf("%s\n", e2.name);
}

funktioniert gut mit neueren Versionen von Compilern. Genau wie bei id wird der Inhalt des Namens in die zugewiesene Strukturvariable kopiert.

Saroj Panda
quelle
1
Noch einfacher: struct emp get () {return {100, "john"}; }
Chris Reid
1

Die Adresse struct var e2 wird als arg an den Angerufenenstapel gesendet, und dort werden Werte zugewiesen. Tatsächlich gibt get () die Adresse von e2 in eax reg zurück. Dies funktioniert wie ein Aufruf per Referenz.

Bala
quelle