2012-04-06 28 views
21

Perché gcc consente dichiarazioni extern di tipo void? È un'estensione o standard C? Ci sono usi accettabili per questo?Perché gcc consente dichiarazioni extern di tipo void (non-pointer)?

sto indovinando che è un'estensione, ma io non lo trovo menzionato:
http://gcc.gnu.org/onlinedocs/gcc-4.3.6/gcc/C-Extensions.html

$ cat extern_void.c 
extern void foo; /* ok in gcc 4.3, not ok in Visual Studio 2008 */ 
void* get_foo_ptr(void) { return &foo; } 

$ gcc -c extern_void.C# no compile error 

$ gcc --version | head -n 1 
gcc (Debian 4.3.2-1.1) 4.3.2 

Definire foo come tipo vuoto è, naturalmente, un errore di compilazione:

$ gcc -c -Dextern= extern_void.c 
extern_void.c:1: error: storage size of ‘foo’ isn’t known 

Per il confronto, Visual Studio 2008 restituisce un errore nella dichiarazione extern:

$ cl /c extern_void.c 
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 15.00.21022.08 for 80x86 
Copyright (C) Microsoft Corporation. All rights reserved. 

extern_void.c 
extern_void.c(1) : error C2182: 'foo' : illegal use of type 'void' 
+0

Ciò che è interessante è che anche con '-std = c89 -pedantic' gcc è bello con questo. – Dave

+0

Come ho capito, la definizione di una variabile di tipo 'void' è mal formata, l'applicazione di' extern' su un tipo incompleto non è mal formata però, Per [esempio] (http://ideone.com/v7VkF): ' extern' su array con dimensione sconosciuta, che viene definita in seguito.Tuttavia, ** §6.2.5.19 ** dice * "Il tipo void comprende un insieme vuoto di valori, è un tipo di oggetto incompleto che non può essere completato." *, dato che il tuo codice dovrebbe essere trattato come una violazione del vincolo. Il fatto che compili in modo pulito con '-pedantic' dice che non è un'estensione questo è bug o ambiguità di gcc in modi in cui msvc e gcc interpretano lo standard. –

risposta

6

Stranamente (o forse non così stranamente ...) mi sembra che GCC abbia ragione ad accettarlo.

Se questo è stato dichiarato static invece di extern, allora sarebbe il collegamento interno, e si applicherebbe §6.9.2/3:

If the declaration of an identifier for an object is a tentative definition and has internal linkage, the declared type shall not be an incomplete type.

Se non ha specificato alcuna classe di archiviazione (extern, in questo caso), allora si applicherebbe §6.7/7:

If an identifier for an object is declared with no linkage, the type for the object shall be complete by the end of its declarator, or by the end of its init-declarator if it has an initializer; in the case of function arguments (including in prototypes), it is the adjusted type (see 6.7.5.3) that is required to be complete.

I entrambi i casi, void non funzionerebbe, perché (§6.2.5/19):

The void type [...] is an incomplete type that cannot be completed.

Nessuna di quelle applicabili, tuttavia. Che sembra lasciare solo i requisiti di §6.7.2/2, che sembra permettere una dichiarazione di un nome con il tipo void:

At least one type specifier shall be given in the declaration specifiers in each declaration, and in the specifier-qualifier list in each struct declaration and type name. Each list of type specifiers shall be one of the following sets (delimited by commas, when there is more than one set on a line); the type specifiers may occur in any order, possibly intermixed with the other declaration specifiers.

  • void
  • char
  • signed char

[ ... more types elided]

non sono sicuro che è davvero intenzionale - Ho il sospetto che il void è veramente inteso per cose come i tipi derivati ​​(ad esempio, puntatore a vuoto) o il tipo di ritorno da una funzione, ma non riesco a trovare nulla che specifica direttamente tale restrizione.

+0

La mia lettura di §6.9.2 è esattamente l'opposto della tua: una dichiarazione di un identificatore per un oggetto che ha scope di file senza un inizializzatore e senza un identificatore di classe di memoria o con lo specificatore di classe di memoria statico, costituisce un definizione provvisoria. Ciò non significa che NON si tratta di una definizione provvisoria e quindi un tipo incompleto NON è permesso? Comeau, IMO, si lamenta correttamente. – dirkgently

+0

@dirkgently: Questa non è sicuramente una definizione provvisoria, che è definita come (§6.9.2/2): "Una dichiarazione di un identificatore per un oggetto che ha scope di file senza un inizializzatore e senza un identificatore di classe di memoria o con lo specificatore di classe di memoria statico, costituisce una * definizione provvisoria *. " Non vedo nulla che dice che un tipo incompleto non è permesso solo perché non è una definizione provvisoria però. –

+1

L'ambiguità tra gcc e msvc sembra essere nell'interpretazione di *** §6.2.5.19 ***, se 'void' è un tipo Incomplete che non può mai essere completato, quindi l'argomento si riduce a dovrebbe essere consentito 'extern' da applicare su un tipo incompleto che non può mai essere completo, credo che lasci spazio all'interpretazione dell'implementazione. In entrambi i casi, ci sarà un errore per ogni compilatore, solo se l'errore viene emesso durante la compilazione o il collegamento è ciò che può essere diverso e sembra probabilmente un caso limite che non vorrebbe troppi problemi. –

1

GCC (anche, LLVM C frontend) è decisamente buggato. Sia Comeau che MS sembrano riportare degli errori.

frammento

del PO ha almeno due definitiva UBS e uno rosso-aringhe:

Da N1570

[UB # 1] Manca main in ambiente ospitato:

J2. Undefined Behavior

[...] A program in a hosted environment does not define a function named main using one of the specified forms (5.1.2.2.1).

[UB # 2] Anche se ignoriamo quanto sopra, rimane ancora il problema di prendere l'indirizzo di un'espressione void che è esplicitamente vietata:

6.3.2.1 Lvalues, arrays, and function designators

1 An lvalue is an expression (with an object type other than void) that potentially designates an object;64)

e:

6.5.3.2 Address and indirection operators

Constraints

1T he operand of the unary & operator shall be either a function designator, the result of a [] or unary * operator, or an lvalue that designates an object that is not a bit-field and is not declared with the register storage-class specifier.

[Nota: l'accento sulla lvalue mio] Inoltre, v'è una sezione nella norma in particolare su void:

6.3.2.2 void

1 The (nonexistent) value of a void expression (an expression that has type void) shall not be used in any way, and implicit or explicit conversions (except to void) shall not be applied to such an expression.

Una definizione di file-scope è un'espressione primaria (6.5). Quindi, sta prendendo l'indirizzo dell'oggetto indicato da foo. A proposito, quest'ultimo invoca UB. Questo è quindi esplicitamente escluso. Ciò che rimane da capito è se rimuovere il qualificatore extern rende la validità di cui sopra oppure no:

Nel nostro caso la, per foo secondo §6.2.2/5:

5 [...] If the declaration of an identifier for an object has file scope and no storage-class specifier, its linkage is external.

cioè anche se abbiamo omesso lo extern e saremmo ancora atterrati nello stesso problema.

+0

È legale prendere l'indirizzo di un lvalue il cui valore non è utilizzabile (ad esempio una variabile locale che non è mai stata scritta in). Se 'pippo' è dichiarato come un identificatore esterno di tipo' void', l'espressione '& pippo' produrrà un valore legittimo di tipo' void * '. C non fornisce alcun mezzo con il quale si possa creare una definizione risolvibile in link per "pippo", ma se un altro linguaggio consente la definizione di tale identificativo, non vedo alcun utile scopo di vietare ai compilatori C di consentire al codice C di accedere al suo indirizzo. – supercat

4

ho trovato l'unico uso legittimo per dichiarare

extern void foo; 

è quando foo è un simbolo di collegamento (un simbolo esterno definito dal linker) che denota l'indirizzo di un oggetto di tipo non specificato.

Ciò è effettivamente utile perché i simboli di collegamento vengono spesso utilizzati per comunicare l'estensione della memoria; indirizzo di inizio della sezione .text, lunghezza della sezione .text, ecc.

Come tale, è importante che il codice utilizzi questi simboli per documentare il loro tipo assegnandoli a un valore appropriato. Ad esempio, se foo è in realtà la lunghezza di una regione di memoria:

uint32_t textLen; 

textLen = (uint32_t)foo; 

Oppure, se foo è l'indirizzo iniziale della stessa regione di memoria:

uint8_t *textStart; 

textStart = (uint8_t *)foo; 

L'unico modo alternativo per fare riferimento a un collegamento simbolo in "C", che io sappia è di dichiararla come una matrice esterna:

extern uint8_t foo[]; 

io in realtà preferisco la dichiarazione void, in quanto rende chiaro che il simbolo definito dal linker non ha un "tipo" intrinseco.

+0

Hai un esempio di codice in cui viene utilizzato questo metodo a cui puoi collegarti? –

+0

Nello scenario in cui 'foo' è un indirizzo reale, penso che dovrebbe essere dichiarato come indirizzo all'interno del codice C. L'unica volta in cui penserei che "void" sarebbe appropriato sarebbe nei casi in cui "foo" non è realmente un indirizzo. IMHO, sarebbe stato utile per lo Standard specificare che il significato di "extern void" è definito dall'implementazione, a condizione che le implementazioni possano documentare e imporre qualsiasi restrizione sull'uso che ritiene opportuno (inclusa la sua messa al bando completa). – supercat

1

Una limitazione della semantica di linker-interazione di C è che non fornisce alcun meccanismo per consentire costanti numeriche di collegamento-tempo.In alcuni progetti, potrebbe essere necessario che gli inizializzatori statici includano valori numerici che non sono disponibili in fase di compilazione ma che saranno disponibili al momento del collegamento. Su alcune piattaforme, ciò può essere ottenuto definendo da qualche parte (ad esempio in un file in linguaggio assembly) un'etichetta il cui indirizzo, se lanciato su int, restituirebbe il valore numerico di interesse. Una definizione extern può quindi essere utilizzata all'interno del file C per rendere l'"indirizzo" di quella cosa disponibile come costante in fase di compilazione.

Questo approccio è molto specifico della piattaforma (come sarebbe qualsiasi cosa usi il linguaggio assembly), ma rende possibili alcuni costrutti che sarebbero altrimenti problematici. Un aspetto un po 'sgradevole è che se l'etichetta è definita in C come un tipo come unsigned char[], ciò darà l'impressione che l'indirizzo possa essere dereferenziato o che l'aritmetica venga eseguito su di esso. Se un compilatore accetterà void foo;, allora (int)&foo convertirà l'indirizzo assegnato dal linker per foo a un numero intero utilizzando la stessa semantica pointer-to-inteer come sarebbe applicabile con qualsiasi altro `void *.

Non credo che abbia mai usato void a tal fine (ho sempre usato extern unsigned char[]), ma penserei void sarebbe più pulito se qualcosa lo ha definito come un'estensione legittima (niente nello standard C richiede che qualsiasi abilità esiste ovunque per creare un simbolo del linker che può essere usato come qualcosa di diverso da un tipo non vuoto specifico, su piattaforme in cui non esisterebbe alcun mezzo per creare un identificatore del linker che un programma C potrebbe definire come extern void, non ci sarebbe bisogno per i compilatori per consentire tale sintassi).

Problemi correlati