Restituzione di struct contenente array

Il seguente codice semplice segfaults sotto gcc 4.4.4

#include typedef struct Foo Foo; struct Foo { char f[25]; }; Foo foo(){ Foo f = {"Hello, World!"}; return f; } int main(){ printf("%s\n", foo().f); } 

Cambiare la linea finale a

  Foo f = foo(); printf("%s\n", ff); 

Funziona bene. Entrambe le versioni funzionano quando compilate con -std=c99 . Sto semplicemente invocando un comportamento indefinito, o è cambiato qualcosa nello standard, che permette al codice di funzionare sotto C99? Perché è in crash sotto C89?

Credo che il comportamento sia indefinito sia in C89 / C90 che in C99.

foo().f è un’espressione di tipo array, in particolare char[25] . C99 6.3.2.1p3 dice:

Tranne quando è l’operando dell’operatore sizeof o unario e operatore, o è una stringa letterale usata per inizializzare una matrice, un’espressione che ha tipo “array di tipo ” viene convertita in un’espressione con tipo “puntatore a tipo ” che punta all’elemento iniziale dell’object array e non è un lvalue. Se l’object array ha una class di archiviazione di registro, il comportamento non è definito.

Il problema in questo caso particolare (un array che è un elemento di una struttura restituita da una funzione) è che non esiste un “object array”. I risultati delle funzioni vengono restituiti in base al valore, quindi il risultato della chiamata a foo() è un valore di tipo struct Foo e foo().f è un valore (non un lvalue) di tipo char[25] .

Questo è, per quanto ne so, l’unico caso in C (fino a C99) in cui è ansible avere un’espressione non-lvalue di tipo array. Direi che il comportamento di tentare di accedervi è indefinito dall’omissione, probabilmente perché gli autori dello standard (comprensibilmente IMHO) non hanno pensato a questo caso. È probabile che si vedano comportamenti diversi con diverse impostazioni di ottimizzazione.

Il nuovo standard C 2011 patching questo caso angolo inventando una nuova class di archiviazione. N1570 (il link è relativo a una bozza precedente alla C11) dice in 6.2.4p8:

Un’espressione non a valore lue con struttura o tipo di unione, in cui la struttura o unione contiene un membro con tipo di matrice (inclusi, in modo ricorsivo, membri di tutte le strutture e unioni contenute) fa riferimento a un object con durata di archiviazione automatica e durata temporanea . La sua vita inizia quando viene valutata l’espressione e il suo valore iniziale è il valore dell’espressione. La sua durata termina quando termina la valutazione dell’espressione completa contenente o del dichiarante completo. Qualsiasi tentativo di modificare un object con una durata temporanea produce un comportamento indefinito.

Quindi il comportamento del programma è ben definito in C11. Finché non si è in grado di ottenere un compilatore conforms al C11, tuttavia, la soluzione migliore è probabilmente quella di memorizzare il risultato della funzione in un object locale (presupponendo che il proprio objective sia lavorare sul codice piuttosto che interrompere i compilatori):

 [...] int main(void ) { struct Foo temp = foo(); printf("%s\n", temp.f); } 

printf è un po ‘divertente, perché è una di quelle funzioni che richiede vararg . Quindi analizziamolo scrivendo una bar funzioni di supporto. Torneremo a printf più tardi.

(Sto usando “gcc (Ubuntu 4.4.3-4ubuntu5) 4.4.3”)

 void bar(const char *t) { printf("bar: %s\n", t); } 

e chiamandolo invece:

 bar(foo().f); // error: invalid use of non-lvalue array 

OK, questo dà un errore. In C e C ++, non è consentito passare un array in base al valore . Puoi aggirare questa limitazione inserendo la matrice all’interno di una struttura, ad esempio void bar2(Foo f) {...}

Ma non stiamo usando questa soluzione alternativa: non è consentito passare l’array in base al valore. Ora, si potrebbe pensare che debba decadere con un char* , consentendo di passare l’array per riferimento. Ma il decadimento funziona solo se l’array ha un indirizzo (cioè è un lvalue). Ma i temporari , come i valori di ritorno dalla funzione, vivono in una terra magica in cui non hanno un indirizzo. Quindi non puoi prendere l’indirizzo & di un temporaneo. In breve, non siamo autorizzati a prendere l’indirizzo di un temporaneo, e quindi non può decadere da un puntatore. Non siamo in grado di passarlo per valore (perché è un array), né per riferimento (perché è temporaneo).

Ho trovato che il codice seguente ha funzionato:

 bar(&(foo().f[0])); 

ma ad essere sincero penso che sia sospetto. Non ha infranto le regole che ho appena elencato?

E per essere completo, funziona perfettamente come dovrebbe:

 Foo f = foo(); bar(ff); 

La variabile f non è temporanea e quindi possiamo (implicitamente, durante il decadimento) prendere il suo indirizzo.

printf, 32 bit contro 64 bit e stranezza

Ho promesso di menzionare di nuovo printf . Secondo quanto sopra, dovrebbe rifiutarsi di passare foo () .f a qualsiasi funzione (incluso printf). Ma printf è divertente perché è una di quelle funzioni vararg. gcc si è permesso di passare la matrice per valore alla printf.

Quando ho compilato e eseguito il codice per la prima volta, era in modalità a 64 bit. Non ho visto la conferma della mia teoria fino a quando non ho compilato a 32 bit (da -m32 a gcc). Abbastanza sicuro ho avuto un segfault, come nella domanda originale. (Avevo ricevuto qualche risultato senza senso, ma nessun segfault, quando in 64 bit).

Ho implementato il mio my_printf (con le sciocchezze vararg) che ha stampato il valore reale del char * prima di provare a stampare le lettere indicate dal char* . L’ho chiamato così:

 my_printf("%s\n", ff); my_printf("%s\n", foo().f); 

e questo è l’output che ho ottenuto ( codice su ideone ):

 arg = 0xffc14eb3 // my_printf("%s\n", ff); // worked fine string = Hello, World! arg = 0x6c6c6548 // my_printf("%s\n", foo().f); // it's about to crash! Segmentation fault 

Il primo valore del puntatore 0xffc14eb3 è corretto (punta ai caratteri “Ciao, mondo!”), Ma guarda il secondo 0x6c6c6548 . Ecco i codici ASCII per l’ Hell (ordine inverso – little endianness o qualcosa del genere). Ha copiato l’array in base al valore in printf e i primi quattro byte sono stati interpretati come un puntatore o intero a 32 bit. Questo puntatore non punta in alcun modo e quindi il programma si blocca quando tenta di accedere a quella posizione.

Penso che questo sia in violazione dello standard, semplicemente in virtù del fatto che non dovremmo essere autorizzati a copiare gli array in base al valore.

Su MacOS X 10.7.2, sia GCC / LLVM 4.2.1 (‘i686-apple-darwin11-llvm-gcc-4.2 (GCC) 4.2.1 (Basato su Apple Inc. build 5658) (LLVM build 2335.15.00)’ ) e GCC 4.6.1 (che ho creato) compilare il codice senza avvisi (sotto -Wall -Wextra ), in entrambe le modalità a 32 e 64 bit. I programmi funzionano tutti senza crash. Questo è quello che mi aspetterei; il codice mi sta bene.

Forse il problema su Ubuntu è un bug nella versione specifica di GCC che è stato risolto?