2013-07-23 11 views
14

Sto leggendo this code from here (in cinese). C'è un pezzo di codice sulla verifica della variabile globale in C. La variabile a è stata definita nel file t.h che è stato incluso due volte. Nel file foo.c definito un struct b con qualche valore e una funzione main. Nel file main.c, definito due variabili senza inizializzato.C la stessa variabile globale definita in diversi file

/* t.h */ 
#ifndef _H_ 
#define _H_ 
int a; 
#endif 

/* foo.c */ 
#include <stdio.h> 
#include "t.h" 

struct { 
    char a; 
    int b; 
} b = { 2, 4 }; 

int main(); 

void foo() 
{ 
    printf("foo:\t(&a)=0x%08x\n\t(&b)=0x%08x\n 
     \tsizeof(b)=%d\n\tb.a=%d\n\tb.b=%d\n\tmain:0x%08x\n", 
     &a, &b, sizeof b, b.a, b.b, main); 
} 

/* main.c */ 
#include <stdio.h> 
#include "t.h" 

int b; 
int c; 

int main() 
{ 
    foo(); 
    printf("main:\t(&a)=0x%08x\n\t(&b)=0x%08x\n 
     \t(&c)=0x%08x\n\tsize(b)=%d\n\tb=%d\n\tc=%d\n", 
     &a, &b, &c, sizeof b, b, c); 
    return 0; 
} 

Dopo usando Ubuntu GCC 4.4.3 compilazione, il risultato è simile di seguito:

foo: (&a)=0x0804a024 
    (&b)=0x0804a014 
    sizeof(b)=8 
    b.a=2 
    b.b=4 
    main:0x080483e4 
main: (&a)=0x0804a024 
    (&b)=0x0804a014 
    (&c)=0x0804a028 
    size(b)=4 
    b=2 
    c=0 

variabile a e b ha lo stesso indirizzo di due funzioni, ma la dimensione di b è cambiato. Non capisco come ha funzionato!

+3

Qual è la tua domanda? –

+2

Usa '% p' per stampare i puntatori, inoltre dovresti aggiungere' pippo' alla tua intestazione. – Nobilis

+0

Se si desidera risolvere il conflitto, dichiarare la variabile var. – snf

risposta

19

State violando "una regola di definizione" di C, e il risultato è un comportamento indefinito.La "regola della definizione unica" non è formalmente indicata nello standard in quanto tale. Stiamo esaminando oggetti in diversi file sorgente (ovvero unità di traduzione), quindi ci occupiamo di "definizioni esterne". Semantica "una definizione esterna" è precisato (C11 6.9 p5):

Un definizione esterna è una dichiarazione esterna che è anche una definizione di una funzione (diverso da una definizione in linea) o un oggetto. Se un identificatore dichiarato con collegamento esterno viene utilizzato in un'espressione (diverso da come parte dell'operando di un operatore sizeof o _Alignof il cui risultato è una costante intera), da qualche parte nell'intero programma deve esserci esattamente una definizione esterna per l'identificatore; altrimenti, non ci deve essere più di uno.

che sostanzialmente significa che stai solo permesso di definire un oggetto al massimo una volta . (La clausola altrimenti consente di non definire affatto un oggetto esterno se non viene mai utilizzato da nessuna parte nel programma.)

Nota che esistono due definizioni esterne per b. Uno è la struttura che si inizializza in foo.c, e l'altro è il tentativo di definizione a main.c, (C11 6.9.2 P1-2):

Se la dichiarazione di un identificatore per un oggetto ha un ambito di file e un inizializzatore, la dichiarazione è una definizione esterna per l'identificatore.

Una dichiarazione di un identificatore per un oggetto che ha presentare portata senza un inizializzatore, e senza specificatore della classe di archiviazione o con la classe di archiviazione specificatore static, costituisce un tentativo definizione. Se un'unità di traduzione contiene una o più definizioni provvisorie per un identificatore e l'unità di traduzione non contiene alcuna definizione esterna per quell'identificatore, allora il comportamento è esattamente come se l'unità di traduzione contenga una dichiarazione dell'ambito del file di quell'identificatore, con il tipo composto come della fine della unità di traduzione, con un inizializzatore pari a 0.

in modo da avere più definizioni di b. Tuttavia, c'è un altro errore, in quanto è stato definito b con tipi diversi. Prima nota che sono consentite più dichiarazioni allo stesso oggetto con collegamento esterno. Tuttavia, quando lo stesso nome viene utilizzato in due file sorgente diversi, tale nome fa riferimento allo stesso oggetto (C11 6.2.2 p2):

Nell'insieme di unità di traduzione e librerie che costituisce un intero programma, ciascuna La dichiarazione di un identificatore particolare con collegamento esterno indica lo stesso oggetto o la funzione .

C pone una limitazione rigorosa sul dichiarazioni allo stesso oggetto (C11 6.2.7 p2):

tutte le dichiarazioni che si riferiscono allo stesso oggetto o funzione devono avere tipo compatibile; in caso contrario, il comportamento non è definito.

Poiché i tipi per b in ciascuno dei file di origine non corrispondono effettivamente, il comportamento non è definito. (Ciò che costituisce un tipo compatibile è descritto in dettaglio in tutta la C11 6.2.7, ma si riduce sostanzialmente verso il basso per essere che i tipi devono corrispondere.)

modo da avere due mancanze per b:

  • Definizioni multiple.
  • Dichiarazioni multiple con tipi incompatibili.

Tecnicamente, la dichiarazione di int a in entrambi i file di origine viola anche la "regola di una definizione". Si noti che a ha linkage esterno (C11 6.2.2 p5):

Se la dichiarazione di un identificatore per un oggetto ha presentare portata e nessun deposito di classe specificatore, il suo collegamento è esterno.

Ma, dalla citazione da C11 6.9.2 in precedenza, quelle int a definizioni timidi sono definizioni esterni, e vi sono ammessi solo uno di quelli dalla citazione da C11 6.9 in alto.

Le normali dichiarazioni di non responsabilità si applicano per comportamento non definito. Tutto può succedere, e questo includerebbe il comportamento che hai osservato.


Un'estensione comune a C è quello di consentire a più definizioni esterne, ed è descritto nello standard C nel informativo allegato J.5 (C11 J.5.11):

Ci possono essere più di una definizione esterna per l'identificatore di un oggetto, con o senza l'uso esplicito della parola chiave extern; se le definizioni non sono in accordo con o più di una è inizializzata, il comportamento non è definito (6.9.2).

(corsivo è mio.) Dal momento che le definizioni di a sono d'accordo, non c'è nulla di male lì, ma le definizioni per b non sono d'accordo. Questa estensione spiega perché il compilatore non si lamenta della presenza di più definizioni. Dalla citazione di C11 6.2.2, il linker tenterà di riconciliare i riferimenti multipli allo stesso oggetto.

I linker utilizzano in genere uno dei due modelli per riconciliare più definizioni dello stesso simbolo in più unità di traduzione. Questi sono il "Modello comune" e il "Modello Rif/Def". Nel "Modello comune", più oggetti con lo stesso nome vengono piegati in un singolo oggetto in uno stile union in modo che l'oggetto assuma le dimensioni della definizione più grande. Nel "Modello Ref/Def", ogni nome esterno deve avere esattamente una definizione.

La toolchain GNU utilizza il "Modello comune" per impostazione predefinita e un "Modello Ref/Def rilassato", in cui applica rigorosamente una regola di definizione per una singola unità di traduzione, ma non si lamenta delle violazioni su più unità di traduzione .

Il "Modello comune" può essere soppresso nel compilatore GNU utilizzando l'opzione -fno-common.Quando ho provato questo sul mio sistema, che ha causato "rigorosa Rif/Def modello" comportamento per il codice simile al vostro:

$ cat a.c 
#include <stdio.h> 
int a; 
struct { char a; int b; } b = { 2, 4 }; 
void foo() { printf("%zu\n", sizeof(b)); } 
$ cat b.c 
#include <stdio.h> 
extern void foo(); 
int a, b; 
int main() { printf("%zu\n", sizeof(b)); foo(); } 
$ gcc -fno-common a.c b.c 
/tmp/ccd4fSOL.o:(.bss+0x0): multiple definition of `a' 
/tmp/ccMoQ72v.o:(.bss+0x0): first defined here 
/tmp/ccd4fSOL.o:(.bss+0x4): multiple definition of `b' 
/tmp/ccMoQ72v.o:(.data+0x0): first defined here 
/usr/bin/ld: Warning: size of symbol `b' changed from 8 in /tmp/ccMoQ72v.o to 4 in /tmp/ccd4fSOL.o 
collect2: ld returned 1 exit status 
$ 

Personalmente ritengo l'ultimo monito lanciato dal linker dovrebbe sempre essere fornita indipendentemente dalla risoluzione modello per più definizioni di oggetti, ma non è né qui né là.


Riferimenti:
Unfortunately, I can't give you the link to my copy of the C11 Standard
What are extern variables in C?
The "Beginner's Guide to Linkers"
SAS Documentation on External Variable Models

+1

Vale la pena notare che due implementazioni tipiche sono quelle solitamente chiamate "def/ref model" e "common model". Quest'ultimo è ciò che usano i normali linker Unix/Linux e dà origine al comportamento osservato. La frase "modello comune" sembra riferirsi ai blocchi di Fortran COMMON implementati su molti mainframe negli anni '60. – torek

+0

@torek: Grazie per avermi spinto a fare qualche ricerca in più. Ho aggiornato la risposta. – jxh

+0

Ingenuo, forse, ma cos'è esattamente un'unità di traduzione? – NickHalden

0

Al momento foo viene compilato, il b che è nell'ambito è il vettore due int {2, 4} o 8 byte quando un sizeof (int) è 4. Quando principale viene compilato, b è appena stato dichiarato nuovamente come int quindi una dimensione di 4 ha senso. Inoltre ci sono probabilmente "padding byte" aggiunti alla struct dopo "a" in modo tale che lo slot successivo (l'int) sia allineato al limite di 4 byte.

-1

a e b hanno gli stessi indirizzi perché si verificano negli stessi punti del file. Il fatto che b sia una dimensione diversa non ha importanza da dove inizia la variabile. Se hai aggiunto una variabile c tra aeb in uno dei file, l'indirizzo di bs sarebbe diverso.

1

b ha lo stesso indirizzo perché il link ha deciso di risolvere il conflitto per conto dell'utente.

sizeof mostra valori diversi perché sizeof viene valutato in tempo di compilazione. A questo punto, il compilatore conosce solo uno b (quello definito nel file corrente).

+1

+1 per sapere che era il linker a mettere il 'b's nella stessa posizione di memoria. – jxh

2

Il pezzo di codice sembra interrompere la regola a una definizione di proposito. Invocherà un comportamento indefinito, non farlo.

Informazioni sulla variabile globale a: non inserire la definizione di una variabile globale in un file di intestazione, poiché verrà inclusa in più file .c e porterà a più definizioni. Basta inserire le dichiarazioni nell'intestazione e inserire la definizione in uno dei file .c.

In t.h:

extern int a; 

In foo.c

int a; 

Circa la variabile globale b: non definiscono più volte, utilizzare static per limitare la variabile in un file.

In foo.c:

static struct { 
    char a; 
    int b; 
} b = { 2, 4 }; 

In main.c

static int b; 
+0

+1 per rimedi specifici per evitare una violazione delle regole di definizione. – jxh

3

Formalmente, è illegale definire la stessa variabile (o funzione) con collegamento esterno più di una volta. Quindi, dal punto di vista formale, il comportamento del tuo programma non è definito.

In pratica, consentire più definizioni della stessa variabile con il collegamento esterno è un'estensione di compilazione popolare (un'estensione comune, menzionata come tale nelle specifiche del linguaggio). Tuttavia, per essere usato correttamente, ogni definizione deve dichiararlo con lo stesso tipo. E non più di una definizione deve includere l'inizializzatore.

Il caso non corrisponde alla descrizione dell'estensione comune. Il codice viene compilato come un effetto collaterale di tale estensione comune, ma il suo comportamento è ancora indefinito.

+0

Avevo già votato questa risposta in precedenza, ma questa risposta ha innanzitutto riguardato specificamente l'estensione comune che consentiva più definizioni esterne. – jxh

Problemi correlati