2012-03-06 10 views
70

Quello che ho capito è che questo non dovrebbe essere fatto, ma credo che ho visto esempi che fanno qualcosa di simile (codice di nota non è necessariamente sintatticamente corretto, ma l'idea c'è)È sicuro restituire una struct in C o C++?

typedef struct{ 
    int a,b; 
}mystruct; 

E poi ecco una funzione

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

ho capito che dovremmo sempre restituire un puntatore ad una struct malloc'ed se vogliamo fare qualcosa di simile, ma sono sicuro che ho visto esempi che fare qualcosa di simile. È corretto? Personalmente restituisco sempre un puntatore a una struttura mallocita o eseguo semplicemente un passaggio facendo riferimento alla funzione e modificando i valori lì. (Perché la mia comprensione è che una volta che l'ambito della funzione è finito, qualsiasi stack è stato usato per allocare la struttura può essere sovrascritto).

Aggiungiamo una seconda parte alla domanda: varia dal compilatore? In caso affermativo, qual è il comportamento delle ultime versioni di compilatori per desktop: gcc, g ++ e Visual Studio?

Riflessioni sulla questione?

+29

"Quello che capisco è che questo non dovrebbe essere fatto", dice chi? Lo sto facendo tutto il tempo. Si noti inoltre che typedef non è necessario in C++ e che non esiste qualcosa come "C/C++". – PlasmaHH

+4

La domanda sembra ** non ** essere indirizzata a C++. –

+3

@PlasmaHH La copia di strutture di grandi dimensioni può risultare inefficiente. Ecco perché bisogna fare attenzione e riflettere seriamente prima di restituire una struttura in base al valore, specialmente se la struttura ha un costoso costruttore di copie e il compilatore non è adatto all'ottimizzazione del valore di ritorno. Recentemente ho apportato un'ottimizzazione a un'app che stava spendendo una parte significativa del suo tempo nei costruttori di copie per alcune grandi strutture che un programmatore aveva deciso di restituire di valore in tutto il mondo. L'inefficienza ci costava circa $ 800.000 nell'hardware del datacenter aggiuntivo che dovevamo acquistare. – Crashworks

risposta

67

È perfettamente sicuro, e non è sbagliato farlo. Inoltre: non varia dal compilatore.

Di solito, quando (come nel tuo esempio) la tua struttura non è troppo grande, direi che questo approccio è anche meglio che restituire una struttura malloc'ed (malloc è un'operazione costosa).

+2

Sarebbe ancora sicuro se uno dei campi fosse un char *? Ora ci sarebbe il puntatore nella struct – jzepeda

+0

Sì, lo farebbe. Basta fare attenzione quando si malloc/libera quel 'char *'. –

+3

@ user963258 in realtà, che dipende da come si implementa il costruttore e il distruttore di copia. –

61

È perfettamente sicuro.

Stai tornando in base al valore. Ciò che porterebbe a un comportamento indefinito è se tu tornassi per riferimento.

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

Il comportamento dello snippet è perfettamente valido e definito. Non varia dal compilatore. Va bene!

Personalmente ho sempre restituire un puntatore ad una struct malloc'ed

Non dovresti. Dovresti evitare la memoria allocata dinamicamente quando possibile.

o semplicemente fare un passaggio con riferimento alla funzione e modificare i valori lì.

Questa opzione è perfettamente valida. È una questione di scelta. In generale, lo fai se vuoi restituire qualcos'altro dalla funzione, mentre modifichi la struttura originale.

Perché la mia comprensione è che una volta che il campo di applicazione della funzione è sopra, qualunque stack è stato utilizzato per allocare la struttura può essere sovrascritta

questo è sbagliato. Intendevo, è un po 'corretto, ma tu restituisci una copia della struttura che crei all'interno della funzione. In teoria. In pratica, è possibile e probabilmente si verificherà RVO.Leggi sull'ottimizzazione del valore di ritorno. Ciò significa che sebbene retval sembri uscire dall'ambito di applicazione al termine della funzione, potrebbe effettivamente essere incorporato nel contesto di chiamata, per impedire la copia aggiuntiva. Questa è un'ottimizzazione che il compilatore è libero di implementare.

+2

+1 per menzionare il RVO. Questa importante ottimizzazione rende effettivamente questo modello fattibile per oggetti con costosi costruttori di copie, come i contenitori STL. – Kos

+1

Vale la pena ricordare che sebbene il compilatore sia libero di eseguire l'ottimizzazione del valore di ritorno, non c'è alcuna garanzia che lo farà. Questo non è qualcosa su cui puoi contare, solo speranza. – Watcom

+0

Ottengo tipi in conflitto ... – delive

7

La durata dell'oggetto mystruct nella funzione termina effettivamente quando si esce dalla funzione. Tuttavia, si passa l'oggetto in base al valore nell'istruzione return. Ciò significa che l'oggetto viene copiato dalla funzione nella funzione di chiamata. L'oggetto originale è sparito, ma la copia è viva.

4

È perfettamente legale, ma con strutture di grandi dimensioni ci sono due fattori che devono essere presi in considerazione: velocità e dimensioni dello stack.

+0

Sentito l'ottimizzazione del valore di ritorno? –

+0

Sì, ma stiamo parlando di restituire la struttura in base al valore e ci sono casi in cui il compilatore non è in grado di eseguire RVO. – ebutusov

+2

Direi che mi preoccupo solo della copia in più dopo aver fatto un po 'di profilazione. –

2

Sarò anche d'accordo con sftrabbit, Life infatti termina e l'area dello stack viene chiarita ma il compilatore è abbastanza intelligente da garantire che tutti i dati vengano recuperati nei registri o in qualche altro modo.

Un semplice esempio di conferma è il seguente. (Tratto da montaggio compilatore Mingw)

_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 

Si può vedere che il valore di b è stato trasmesso attraverso edx. mentre il eax predefinito contiene il valore per a.

3

Un tipo di struttura può essere il tipo per il valore restituito da una funzione. È sicuro perché il compilatore sta per creare una copia di struct e restituire la copia non la struttura locale nella funzione.

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

È perfettamente sicuro restituire una struttura come si è fatto.

Sulla base di questa affermazione però: Perché la mia comprensione è che una volta che il campo di applicazione della funzione è finita, qualunque sia pila è stato utilizzato per allocare la struttura possono essere sovrascritti, vorrei solo immaginare uno scenario in cui uno qualsiasi dei membri del la struttura è stata allocata dinamicamente (malloc'ed o new'ed), nel qual caso, senza RVO, i membri allocati dinamicamente verranno distrutti e la copia restituita avrà un membro che punta alla spazzatura.

+0

Lo stack viene utilizzato solo temporaneamente per l'operazione di copia. Di solito, lo stack verrebbe prenotato prima della chiamata e la funzione chiamata colloca i dati da restituire nello stack, quindi il chiamante estrae questi dati dallo stack e li memorizza ovunque venga assegnato. Quindi, non preoccuparti. –

3

La sicurezza dipende da come è stata implementata la struttura stessa. Mi sono imbattuto in questa domanda mentre implementavo qualcosa di simile, ed ecco il problema potenziale.

Il compilatore, quando restituisce il valore fa alcune operazioni (tra possibilmente altri):

  1. chiama il costruttore di copia mystruct(const mystruct&) (this è una variabile temporanea fuori la funzione func assegnata dal compilatore stesso)
  2. chiama il distruttore ~mystruct sulla variabile che è stato assegnato all'interno func
  3. chiamate mystruct::operator= se il valore restituito è Assig ned a qualcosa d'altro con =
  4. chiama il distruttore ~mystruct sulla variabile temporanea utilizzata dal compilatore

Ora, se mystruct è semplice come quella descritta qui tutto va bene, ma se ha puntatore (come char*) o più complicata gestione della memoria, quindi tutto dipende da come sono implementati mystruct::operator=, mystruct(const mystruct&) e ~mystruct. Pertanto, suggerisco cautele quando restituisco strutture di dati complesse come valore.

1

Non è sicuro restituire una struttura. Mi piace farlo da solo, ma se qualcuno aggiungerà un costruttore di copia alla struttura restituita in seguito, verrà chiamato il costruttore della copia. Questo potrebbe essere inaspettato e può rompere il codice. Questo bug è molto difficile da trovare.

Avevo una risposta più elaborata, ma al moderatore non piaceva. Quindi, a tue spese, il mio consiglio è breve.

+0

"Non è sicuro restituire una struttura. [...] verrà chiamato il costruttore di copie. "- C'è una differenza tra * safe * e * inefficient *. Restituire una struttura è sicuramente sicuro. Anche in questo caso, la chiamata al copy ctor sarà molto probabilmente eluita dal compilatore in quanto la struct viene creata nello stack del chiamante per iniziare. – phg

1

Aggiungiamo una seconda parte alla domanda: Varia a seconda del compilatore?

In effetti lo fa, come ho scoperto al mio dolore: http://sourceforge.net/p/mingw-w64/mailman/message/33176880/

stavo usando gcc su win32 (MinGW) per chiamare interfacce COM che hanno restituito le strutture. Risulta che MS lo fa in modo diverso da GNU e quindi il mio programma (gcc) si è bloccato con uno stack distrutto.

Potrebbe essere che MS potrebbe avere il terreno più elevato qui - ma tutto quello che mi interessa è la compatibilità ABI tra MS e GNU per costruire su Windows.

Se è così, allora che cosa è il comportamento per le ultime versioni di compilatori per i desktop: gcc, g ++ e Visual Studio

Potete trovare alcuni messaggi su una mailing Wine list su come MS sembra farlo.

+0

Sarebbe più utile se tu dessi un puntatore alla mailing list Wine a cui ti stai riferendo. –

+0

Il ritorno delle strutture va bene. COM specifica un'interfaccia binaria; se qualcuno non implementa correttamente COM, sarebbe un bug. –

5

Non solo è sicuro per restituire un struct in C (o di una class in C++, dove struct -s sono in realtà class -es con predefinito public: membri), ma un sacco di software che sta facendo.

Ovviamente, quando si restituisce un class in C++, la lingua specifica che verrà chiamato un distruttore o un costruttore di movimento, ma in molti casi questo potrebbe essere ottimizzato dal compilatore.

Inoltre, Linux x86-64 ABI specifica che restituisce un struct con due scalare (ad esempio puntatori o long) valori avviene attraverso registri (%rax & %rdx) quindi è molto veloce ed efficiente. Quindi, per quel caso particolare, è probabilmente più veloce restituire tali campi a due scalari struct piuttosto che fare qualsiasi altra cosa (ad esempio memorizzarli in un puntatore passato come argomento).

Restituzione di un tale campo di due scalare struct è quindi molto più veloce di malloc -ing e restituire un puntatore.