Cosa succede se definisco un array di dimensioni 0 in C / C ++?


127

Solo curioso, cosa succede realmente se definisco un array di lunghezza zero int array[0];nel codice? GCC non si lamenta affatto.

Programma di esempio

#include <stdio.h>

int main() {
    int arr[0];
    return 0;
}

Una precisazione

In realtà sto cercando di capire se gli array di lunghezza zero inizializzati in questo modo, invece di essere indicati come la lunghezza variabile nei commenti di Darhazer, siano ottimizzati o meno.

Questo perché devo rilasciare un po 'di codice nel jolly, quindi sto cercando di capire se devo gestire i casi in cui SIZEè definito come 0, cosa che accade in alcuni codici con una definizione staticaint array[SIZE];

In realtà sono rimasto sorpreso dal fatto che GCC non si lamenti, il che ha portato alla mia domanda. Dalle risposte che ho ricevuto, credo che la mancanza di un avviso sia in gran parte dovuta al supporto del vecchio codice che non è stato aggiornato con la nuova sintassi [].

Poiché mi chiedevo principalmente l'errore, sto etichettando la risposta di Lundin come corretta (quella di Nawaz era prima, ma non era così completa) - gli altri stavano sottolineando il suo uso effettivo per le strutture imbottite di coda, sebbene pertinente, non è ' t esattamente quello che stavo cercando.


51
@AlexanderCorwin: Sfortunatamente in C ++, con comportamento indefinito, estensioni non standard e altre anomalie, provare qualcosa da soli spesso non è un percorso verso la conoscenza.
Benjamin Lindley,

5
@JustinKirk Anche io sono rimasto intrappolato da questo test e vedendolo ha funzionato. E a causa delle critiche che ho ricevuto nel mio post, ho imparato che testarlo e vederlo funzionare non significa che sia valido e legale. Quindi un autotest non è valido a volte.
StormByte

2
@JustinKirk, vedi la risposta di Matthieu per un esempio di dove la useresti . Potrebbe anche rivelarsi utile in un modello in cui la dimensione dell'array è un parametro modello. L'esempio nella domanda è ovviamente fuori contesto.
Mark Ransom,

2
@JustinKirk: Qual è lo scopo di []in Python o anche ""in C? A volte, hai una funzione o una macro che richiede un array, ma non hai dati da inserire.
dan04,

15
Che cos'è "C / C ++"? Queste sono due lingue separate
gare di leggerezza in orbita

Risposte:


86

Un array non può avere dimensioni zero.

ISO 9899: 2011 6.7.6.2:

Se l'espressione è un'espressione costante, deve avere un valore maggiore di zero.

Il testo sopra è vero sia per un array semplice (paragrafo 1). Per un VLA (array di lunghezza variabile), il comportamento non è definito se il valore dell'espressione è inferiore o uguale a zero (paragrafo 5). Questo è un testo normativo nello standard C. A un compilatore non è consentito implementarlo in modo diverso.

gcc -std=c99 -pedantic dà un avvertimento per il caso non VLA.


34
"deve effettivamente dare un errore" - la distinzione tra "avvertimenti" ed "errori" non è riconosciuta nello standard (menziona solo "diagnostica") e l'unica situazione in cui la compilazione deve fermarsi [cioè la differenza nel mondo reale tra avviso ed errore] sta incontrando una #errordirettiva.
Casuale 832

12
Cordiali saluti, come regola generale, gli standard (C o C ++) dichiarano solo ciò che i compilatori devono consentire , ma non ciò che devono impedire . In alcuni casi, dichiareranno che il compilatore dovrebbe emettere una "diagnostica", ma è più o meno specifico. Il resto è lasciato al fornitore del compilatore. EDIT: anche quello che ha detto Random832.
mcmcc,

8
@Lundin "A un compilatore non è consentito creare un file binario contenente matrici di lunghezza zero." Lo standard non dice assolutamente nulla del genere. Dice solo che deve generare almeno un messaggio diagnostico quando viene dato il codice sorgente contenente un array con un'espressione costante di lunghezza zero per le sue dimensioni. L'unica circostanza in cui lo standard proibisce a un compilatore di creare un binario è se incontra una #errordirettiva preprocessore.
Casuale 832

5
@Lundin La generazione di un file binario per tutti i casi corretti soddisfa il n. 1 e la generazione o non generazione di uno binario per i casi errati non influirebbe su di esso. La stampa di un avviso è sufficiente per # 3. Questo comportamento non ha rilevanza per il n. 2, poiché lo standard non definisce il comportamento di questo codice sorgente.
Casuale 832

13
@Lundin: Il punto è che la tua affermazione è sbagliata; compilatori conformi sono permesso di costruire un binario contenente un array di lunghezza zero, fino a quando una diagnostica viene rilasciato.
Keith Thompson,

85

Secondo lo standard, non è consentito.

Tuttavia è stata pratica corrente nei compilatori C trattare queste dichiarazioni come una dichiarazione FAM (flessibile array member ) :

C99 6.7.2.1, §16 : come caso speciale, l'ultimo elemento di una struttura con più di un membro nominato può avere un tipo di array incompleto; questo è chiamato un membro flessibile dell'array.

La sintassi standard di una FAM è:

struct Array {
  size_t size;
  int content[];
};

L'idea è che lo si dovrebbe allocare così:

void foo(size_t x) {
  Array* array = malloc(sizeof(size_t) + x * sizeof(int));

  array->size = x;
  for (size_t i = 0; i != x; ++i) {
    array->content[i] = 0;
  }
}

Potresti anche usarlo staticamente (estensione gcc):

Array a = { 3, { 1, 2, 3 } };

Questo è anche noto come strutture imbottite di coda (questo termine precede la pubblicazione dello standard C99) o hack di struttura (grazie a Joe Wreschnig per averlo sottolineato).

Tuttavia questa sintassi è stata standardizzata (e gli effetti garantiti) solo recentemente in C99. Prima era necessaria una dimensione costante.

  • 1 era la strada portatile da percorrere, sebbene fosse piuttosto strana.
  • 0 era più bravo a indicare l'intento, ma non era legale per quanto riguarda lo Standard e supportato come estensione da alcuni compilatori (incluso gcc).

La pratica del padding della coda, tuttavia, si basa sul fatto che lo spazio di archiviazione è disponibile (attenzione malloc), quindi non è adatto all'uso dello stack in generale.


@Lundin: non ho visto alcun VLA qui, tutte le dimensioni sono note al momento della compilazione. Il termine flessibile dell'array proviene da gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Zero-Length.html e si qualifica int content[];qui per quanto ho capito. Dal momento che non sono troppo esperto in termini di arte C ... potresti confermare se il mio ragionamento sembra corretto?
Matthieu M.

@MatthieuM .: C99 6.7.2.1, §16: come caso speciale, l'ultimo elemento di una struttura con più di un membro nominato può avere un tipo di array incompleto; questo è chiamato un membro flessibile dell'array.
Christoph

Questo linguaggio è anche noto con il nome "struct hack" , e ho incontrato più persone che hanno familiarità con quel nome di "struttura a coda chiusa" (mai sentito prima, tranne forse come riferimento generico al riempimento di una struttura per la futura compatibilità ABI ) o "membro flessibile dell'array" che ho ascoltato per la prima volta in C99.

1
L'uso di una dimensione dell'array di 1 per l'hack strutt avrebbe evitato di avere squawk di compilatori, ma era solo "portatile" perché gli autori di compilatori erano abbastanza gentili da riconoscere tale utilizzo come standard di fatto. Se non fosse per il divieto di matrici di dimensioni zero, il conseguente uso da parte del programmatore di matrici a singolo elemento come sostituto scadente e l'atteggiamento storico degli scrittori del compilatore che dovrebbero soddisfare le esigenze del programmatore anche quando non richiesto dallo Standard, gli autori del compilatore potrebbero sono stati facilmente e utilmente ottimizzati foo[x]per foo[0]ogni volta che fooera un array a elemento singolo.
supercat

1
@RobertSsupportsMonicaCellio: come mostrato esplicitamente nella risposta, ma alla fine . Ho anche caricato anticipatamente la spiegazione, per renderla più chiara sin dall'inizio.
Matthieu M.

58

In Standard C e C ++, l'array di dimensioni zero non è consentito.

Se stai usando GCC, compilarlo con l' -pedanticopzione. Darà un avvertimento , dicendo:

zero.c:3:6: warning: ISO C forbids zero-size array 'a' [-pedantic]

Nel caso di C ++, fornisce un avviso simile.


9
In Visual C ++ 2010:error C2466: cannot allocate an array of constant size 0
Mark Ransom

4
-Werror trasforma semplicemente tutti gli avvisi in errori, il che non risolve il comportamento errato del compilatore GCC.
Lundin,

Anche C ++ Builder 2009 dà correttamente un errore:[BCC32 Error] test.c(3): E2021 Array must have at least one element
Lundin

1
Invece di -pedantic -Werror, potresti anche solo fare-pedantic-errors
Stephan Dollberg

3
Un array di dimensioni zero non è esattamente la stessa cosa di un array di dimensioni zero std::array. (A parte: ricordo che non riesco a trovare la fonte che i VLA sono stati considerati e esplicitamente respinti dall'essere in C ++.)

27

È totalmente illegale, e lo è sempre stato, ma molti compilatori trascurano di segnalare l'errore. Non sono sicuro del perché tu voglia farlo. L'unico uso che conosco è di innescare un errore di compilazione da un valore booleano:

char someCondition[ condition ];

Se conditionè falso, viene visualizzato un errore di compilazione. Poiché i compilatori lo consentono, tuttavia, ho iniziato a utilizzare:

char someCondition[ 2 * condition - 1 ];

Questo dà una dimensione di 1 o -1 e non ho mai trovato un compilatore che accetterebbe una dimensione di -1.


Questo è un trucco interessante per usarlo.
Alex Koay,

10
È un trucco comune nella metaprogrammazione, credo. Non sarei sorpreso se le implementazioni STATIC_ASSERTlo usassero.
James Kanze,

Perché non solo:#if condition \n #error whatever \n #endif
Jerfov2

1
@ Jerfov2 perché la condizione potrebbe non essere nota al momento della preelaborazione, solo tempo di compilazione
rmeador

9

Aggiungerò che c'è un'intera pagina della documentazione online di gcc su questo argomento.

Alcune citazioni:

Le matrici di lunghezza zero sono consentite in GNU C.

In ISO C90, dovresti dare ai contenuti una lunghezza di 1

e

Le versioni GCC precedenti alla 3.0 consentivano di inizializzare staticamente array di lunghezza zero, come se fossero array flessibili. Oltre a quei casi che erano utili, consentiva anche le inizializzazioni in situazioni che avrebbero danneggiato i dati successivi

così potresti

int arr[0] = { 1 };

e boom :-)


Posso fare mi piace int a[0], allora a[0] = 1 a[1] = 2??
Suraj Jain,

2
@SurajJain Se vuoi sovrascrivere il tuo stack :-) C non controlla l'indice rispetto alle dimensioni dell'array che stai scrivendo, quindi puoi a[100000] = 5ma se sei fortunato ti arresti semplicemente in crash la tua app, se sei fortunato: -)
xanatos il

Int a [0]; significa un array variabile (array di dimensioni zero), Come posso ora assegnarlo
Suraj Jain,

@SurajJain Quale parte di "C non controlla l'indice rispetto alle dimensioni dell'array che stai scrivendo" non è chiara? Non esiste un controllo indice in C, è possibile scrivere dopo la fine dell'array e arrestare in modo anomalo il computer o sovrascrivere preziosi frammenti di memoria. Quindi, se hai una matrice di 0 elementi, puoi scrivere dopo la fine degli 0 elementi.
xanatos,


9

Un altro uso di matrici di lunghezza zero è per creare oggetti di lunghezza variabile (pre-C99). Le matrici di lunghezza zero sono diverse dalle matrici flessibili che hanno [] senza 0.

Citato da gcc doc :

Le matrici di lunghezza zero sono consentite in GNU C. Sono molto utili come ultimo elemento di una struttura che è in realtà un'intestazione per un oggetto di lunghezza variabile:

 struct line {
   int length;
   char contents[0];
 };
 
 struct line *thisline = (struct line *)
   malloc (sizeof (struct line) + this_length);
 thisline->length = this_length;

In ISO C99, useresti un membro flessibile dell'array, che è leggermente diverso nella sintassi e nella semantica:

  • I membri dell'array flessibile vengono scritti come contenuto [] senza lo 0.
  • I membri flessibili dell'array hanno un tipo incompleto e pertanto l'operatore sizeof potrebbe non essere applicato.

Un esempio del mondo reale sono le matrici di lunghezza zero di struct kdbus_itemin kdbus.h (un modulo del kernel Linux).


2
IMHO, non vi era alcun motivo valido per cui lo Standard vietasse le matrici di lunghezza zero; potrebbe avere oggetti di dimensioni pari a zero come membri di una struttura e considerarli come void*scopi aritmetici (pertanto sarebbe vietato aggiungere o sottrarre puntatori a oggetti di dimensione zero). Mentre i membri di array flessibili sono per lo più migliori di array di dimensioni zero, possono anche agire come una sorta di "unione" per alias cose senza aggiungere un ulteriore livello di indiretto "sintattico" a ciò che segue (ad esempio, dato che struct foo {unsigned char as_bytes[0]; int x,y; float z;}uno può accedere ai membri x.. z...
supercat,

... direttamente senza dover dire ad esempio myStruct.asFoo.x, ecc. Inoltre, IIRC, C squawks in qualsiasi sforzo per includere un membro di array flessibile all'interno di una struttura, rendendo così impossibile avere una struttura che includa più altri membri di array flessibile di lunghezza nota soddisfare.
Supercat,

@supercat è un buon motivo per mantenere l'integrità della regola sull'accesso ai limiti dell'array esterno. Come ultimo membro di una struttura, il membro della matrice flessibile C99 ottiene esattamente lo stesso effetto della matrice di dimensioni zero GCC, ma senza la necessità di aggiungere casi speciali ad altre regole. IMHO è un miglioramento che sizeof x->contentsè un errore in ISO C invece di restituire 0 in gcc. Le matrici di dimensioni zero che non sono membri di struttura presentano una serie di altri problemi.
MM,

@MM: Quali problemi potrebbero causare se la sottrazione di due puntatori uguali a un oggetto di dimensioni zero fosse definita come cedente zero (così come la sottrazione di puntatori uguali a qualsiasi dimensione di oggetto) e la sottrazione di puntatori disuguali a oggetti di dimensioni zero era definito come cedente Valore non specificato? Se lo Standard avesse specificato che un'implementazione può consentire l'incorporazione di una struttura contenente una FAM all'interno di un'altra struttura a condizione che l'elemento successivo in quest'ultima struttura sia una matrice con lo stesso tipo di elemento della FAM o una struttura che inizi con tale matrice , e purché ...
supercat

... riconosce la FAM come alias della matrice (se le regole di allineamento farebbero atterrare le matrici a diversi offset, sarebbe necessaria una diagnostica), sarebbe stato molto utile. Così com'è, non c'è un buon modo per avere un metodo che accetta i puntatori a strutture del formato generale struct {int n; THING dat[];}e può lavorare con cose di durata statica o automatica.
supercat

6

Le dichiarazioni di array di dimensioni zero all'interno di strutture sarebbero utili se fossero consentite e se la semantica fosse tale che (1) forzerebbero l'allineamento ma non allocerebbero alcuno spazio e (2) indicizzare l'array sarebbe considerato un comportamento definito nella caso in cui il puntatore risultante sarebbe all'interno dello stesso blocco di memoria della struttura. Tale comportamento non è mai stato consentito da alcuno standard C, ma alcuni compilatori più vecchi lo hanno consentito prima che diventasse standard per i compilatori per consentire dichiarazioni di array incomplete con parentesi vuote.

La struttura hack, come comunemente implementata usando un array di dimensioni 1, è pericolosa e non credo che ci sia alcun requisito che i compilatori si astengano dal romperlo. Ad esempio, mi aspetterei che se un compilatore vede int a[1], sarebbe nel suo pieno diritto a considerare a[i]come a[0]. Se qualcuno cerca di aggirare i problemi di allineamento della struttura hack tramite qualcosa del genere

typedef struct {
  dimensione uint32_t;
  uint8_t data [4]; // Usa quattro, per evitare che l'imbottitura riduca le dimensioni della struttura
}

un compilatore potrebbe diventare intelligente e supporre che la dimensione dell'array sia davvero quattro:

; Come è scritto
  foo = myStruct-> data [i];
; Come interpretato (assumendo hardware little-endian)
  foo = ((* (uint32_t *) myStruct-> data) >> (i << 3)) & 0xFF;

Tale ottimizzazione potrebbe essere ragionevole, soprattutto se myStruct->datapotesse essere caricata in un registro nella stessa operazione di myStruct->size. Non so nulla nello standard che proibisca tale ottimizzazione, anche se ovviamente spezzerebbe qualsiasi codice che potrebbe aspettarsi di accedere a cose oltre il quarto elemento.


1
Il membro flessibile dell'array è stato aggiunto a C99 come versione legittima dell'hack strutt
MM

Lo standard afferma che gli accessi a diversi membri dell'array non sono in conflitto, il che tenderebbe a rendere impossibile tale ottimizzazione.
Ben Voigt,

@BenVoigt: lo standard del linguaggio C non specifica l'effetto della scrittura di un byte e della lettura simultanea del contenuto di una parola, ma il 99,9% dei processori specifica che la scrittura avrà esito positivo e la parola conterrà la versione nuova o precedente del byte insieme al contenuto inalterato degli altri byte. Se un compilatore prende di mira tali processori, quale sarebbe il conflitto?
supercat

@supercat: lo standard del linguaggio C garantisce che le scritture simultanee su due diversi elementi dell'array non entrino in conflitto. Quindi il tuo argomento che (leggi mentre scrivi) funziona bene, non è sufficiente.
Ben Voigt,

@BenVoigt: se un pezzo di codice dovesse ad esempio scrivere sugli elementi dell'array 0, 1 e 2 in una sequenza, non sarebbe permesso leggere tutti e quattro gli elementi in un lungo, modificarne tre e riscrivere tutti e quattro, ma io penso che sarebbe permesso leggere tutti e quattro in un lungo, modificarne tre, riscrivere i 16 bit inferiori come un corto e bit 16-23 come un byte. Non saresti d'accordo con quello? E il codice che aveva solo bisogno di leggere elementi dell'array avrebbe potuto semplicemente leggerli in un lungo e usarli.
supercat,
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.