Restituisce uno `struct` da una funzione in C


171

Oggi stavo insegnando a un paio di amici come usare C structs. Uno di loro ha chiesto se si poteva restituire una structda una funzione, a cui ho risposto: "No! È piacerebbe tornare puntatori in modo dinamico malloced struct. S al posto"

Venendo da qualcuno che fa principalmente C ++, mi aspettavo di non essere in grado di restituire structi valori. In C ++ puoi sovraccaricare l'oggetto operator =per i tuoi oggetti e ha perfettamente senso avere una funzione per restituire l'oggetto in base al valore. In C, tuttavia, non hai questa opzione e quindi mi ha fatto pensare a cosa sta effettivamente facendo il compilatore. Considera quanto segue:

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;
}    

Capisco perché struct b = a;non dovrebbe funzionare: non è possibile sovraccaricare operator =il tipo di dati. Com'è che si a = foo();compila bene? Significa qualcosa di diverso da struct b = a;? Forse la domanda da porsi è: che cosa fa esattamente la returndichiarazione congiunta per =firmare?

[modifica]: Ok, mi è stato appena indicato che struct b = aè un errore di sintassi: è corretto e sono un idiota! Ma questo lo rende ancora più complicato! L'uso struct MyObj b = afunziona davvero! Cosa mi sto perdendo qui?


24
struct b = a;è un errore di sintassi. E se ci provi struct MyObj b = a;?
Greg Hewgill l'

2
@GregHewgill: hai assolutamente ragione. Abbastanza interessante, tuttavia, struct MyObj b = a;sembra funzionare :)
mmirzadeh

Risposte:


200

È possibile restituire una struttura da una funzione (o utilizzare l' =operatore) senza problemi. È una parte ben definita della lingua. L'unico problema struct b = aè che non hai fornito un tipo completo. struct MyObj b = afunzionerà bene. Puoi anche passare strutture a funzioni: una struttura è esattamente la stessa di qualsiasi tipo incorporato ai fini del passaggio dei parametri, dei valori di ritorno e dell'assegnazione.

Ecco un semplice programma dimostrativo che fa tutte e tre le cose: passa una struttura come parametro, restituisce una struttura da una funzione e usa le strutture nelle istruzioni di assegnazione:

#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;
}

Il prossimo esempio è praticamente lo stesso, ma utilizza il tipo incorporato a intscopo dimostrativo. I due programmi hanno lo stesso comportamento rispetto al valore pass-by per il passaggio dei parametri, l'assegnazione, ecc .:

#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;
}

14
È abbastanza interessante. Ho sempre avuto l'impressione che tu abbia bisogno di suggerimenti per questi. Ho sbagliato :)
mmirzadeh

8
Certamente non hai bisogno di puntatori. Detto questo, la maggior parte delle volte vorresti usarli - le copie di memoria implicite che avvengono facendo oscillare le strutture in base al valore possono essere un vero spreco di cicli della CPU, per non parlare della larghezza di banda della memoria.
Carl Norum,

10
@CarlNorum Quanto è grande una struttura per ottenere una copia che costa più di malloc + gratis?
josefx,

7
@josefx, una singola copia? Probabilmente enorme. Il fatto è che normalmente se passi le strutture per valore le stai copiando molto . Comunque non è così semplice. Potresti passare in giro per strutture locali o globali, nel qual caso il costo di allocazione è praticamente gratuito.
Carl Norum,

7
Sono necessari puntatori e allocazione della memoria per il valore restituito al di fuori del corpo della funzione non appena la quantità di memoria allocata per un valore non è nota al momento della compilazione. È per le strutture, quindi le funzioni C non hanno problemi a restituirle.
reinierpost,

33

Quando si effettua una chiamata come ad esempio a = foo();, il compilatore potrebbe inserire nello stack l' indirizzo della struttura dei risultati e passarlo come puntatore "nascosto" alla foo()funzione. In effetti, potrebbe diventare qualcosa di simile:

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

foo(&a);

Tuttavia, l'implementazione esatta di questo dipende dal compilatore e / o dalla piattaforma. Come osserva Carl Norum, se la struttura è abbastanza piccola, potrebbe addirittura essere completamente restituita in un registro.


11
Dipende totalmente dall'implementazione. Ad esempio, armcc passerà strutture abbastanza piccole nei normali registri dei parametri che passano (o restituiscono valore).
Carl Norum,

Non restituirebbe un puntatore a una variabile locale? La memoria per la struttura restituita non può far parte del fooframe dello stack. Deve essere in un posto che sopravvive oltre il ritorno di foo.
Anders Abel,

@AndersAbel: Penso che Greg voglia dire che il compilatore prende un puntatore alla variabile nella funzione principale e la passa alla funzione foo. All'interno della funzione foo, fai semplicemente il compito
mmirzadeh

4
@AndersAbel: Alla *r = afine farebbe (effettivamente) una copia della variabile locale alla variabile del chiamante. Dico "efficacemente" perché il compilatore potrebbe implementare RVO ed eliminare completamente la variabile locale a.
Greg Hewgill l'

3
Sebbene questo non risponda direttamente alla domanda, questo è il motivo per cui molte persone cadranno qui tramite Google c return struct: sanno che in cdecl eaxviene restituito per valore e che le strutture in generale non rientrano all'interno eax. Questo è quello che stavo cercando.
Ciro Santilli 22 冠状 病 六四 事件 法轮功

14

La struct blinea non funziona perché è un errore di sintassi. Se lo espandi per includere il tipo funzionerà bene

struct MyObj b = a;  // Runs fine

Ciò che C sta facendo qui è essenzialmente uno memcpydalla struttura sorgente alla destinazione. Questo vale sia per l'assegnazione che per la restituzione dei structvalori (e in realtà ogni altro valore in C)


+1, in effetti, molti compilatori emetteranno effettivamente una chiamata letterale memcpyin questo caso - almeno, se la struttura è abbastanza grande.
Carl Norum,

Quindi, durante l'inizializzazione di un tipo di dati, la funzione memcpy funziona ??
bhuwansahni,

1
@bhuwansahni Non sono sicuro di cosa tu stia chiedendo qui. Potresti elaborare un po '?
JaredPar l'

4
@JaredPar - i compilatori spesso chiamano letteralmente la memcpyfunzione per le situazioni della struttura. Puoi fare un programma di test rapido e vedere GCC farlo, per esempio. Per i tipi integrati che non accadranno, non sono abbastanza grandi da attivare quel tipo di ottimizzazione.
Carl Norum,

3
È sicuramente possibile realizzarlo - il progetto a cui sto lavorando non ha un memcpysimbolo definito, quindi spesso ci imbattiamo in errori del linker "simbolo indefinito" quando il compilatore decide di sputare uno da solo.
Carl Norum,

9

sì, è possibile che possiamo passare anche struttura e struttura di ritorno. Avevi ragione ma in realtà non hai passato il tipo di dati che dovrebbe essere simile a questa struttura MyObj b = a.

In realtà sono anche venuto a sapere quando stavo cercando di trovare una soluzione migliore per restituire più di un valore per la funzione senza usare il puntatore o la variabile globale.

Ora di seguito è riportato l'esempio per lo stesso, che calcola la deviazione dei voti di uno studente sulla media.

#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;
}

5

Per quanto posso ricordare, le prime versioni di C consentivano solo di restituire un valore che poteva rientrare in un registro del processore, il che significa che si poteva restituire solo un puntatore a una struttura. La stessa restrizione si applicava agli argomenti delle funzioni.

Le versioni più recenti consentono di passare oggetti dati più grandi come le strutture. Penso che questa funzione fosse già comune negli anni ottanta o all'inizio degli anni novanta.

Le matrici, tuttavia, possono ancora essere passate e restituite solo come puntatori.


È possibile restituire un array in base al valore se lo si inserisce in uno struct. Ciò che non è possibile restituire in base al valore è un array di lunghezza variabile.
han

1
Sì, posso inserire un array all'interno di una struttura, ma ad esempio non posso scrivere typedef char arr [100]; arr foo () {...} Non è possibile restituire un array, anche se la dimensione è nota.
Giorgio

Il downvoter potrebbe spiegare il motivo del downvote? Se la mia risposta contiene informazioni errate, sarei felice di risolverlo.
Giorgio,

4

È possibile assegnare strutture in C. a = b; è sintassi valida.

Hai semplicemente lasciato fuori parte del tipo - il tag struct - nella tua riga che non funziona.


4

Non vi è alcun problema nel restituire una struttura. Sarà passato per valore

Ma cosa succede se la struttura contiene un membro che ha un indirizzo di una variabile locale

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);
}

Ora, qui e1.name contiene un indirizzo di memoria locale per la funzione get (). Una volta restituito get (), l'indirizzo locale per nome sarebbe stato liberato. Quindi, nel chiamante se proviamo ad accedere a quell'indirizzo, potrebbe causare un errore di segmentazione, poiché stiamo provando un indirizzo liberato. Non va bene..

Dove come e1.id sarà perfettamente valido in quanto il suo valore verrà copiato in e2.id

Quindi, dovremmo sempre cercare di evitare di restituire gli indirizzi di memoria locale di una funzione.

Tutto ciò che è stato rubato può essere restituito come e quando desiderato


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);
}

funziona bene con le versioni più recenti di compilatori. Proprio come l'id, il contenuto del nome viene copiato nella variabile di struttura assegnata.


1
Ancora più semplice: struct emp get () {return {100, "john"}; }
Chris Reid,

1

L'indirizzo struct var e2 viene spinto come arg per chiamare lo stack e i valori vengono assegnati lì. In effetti, get () restituisce l'indirizzo di e2 in eax reg. Funziona come una chiamata per riferimento.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.