2012-06-20 13 views
5

ho avuto un problema strano che ho ridotto al seguente test case:Accesso globali statiche in una funzione inline

inl.h:

inline const char *fn() { return id; } 

a.cc:

#include <stdio.h> 

static const char *id = "This is A"; 

#include "inl.h" 

void A() 
{ 
    printf("In A we get: %s\n", fn()); 
} 

b.cc:

#include <stdio.h> 

static const char *id = "This is B"; 

#include "inl.h" 

void B() 
{ 
    printf("In B we get: %s\n", fn()); 
} 

extern void A(); 

int main() 
{ 
    A(); 
    B(); 
    return 0; 
} 

Ora quando compilo questo con g++ -O1 a.cc b.cc sembra funzionare correttamente. Ottengo:

In A we get: This is A 
In B we get: This is B 

ma se compilo con g++ -O0 a.cc b.cc ottengo:

In A we get: This is A 
In B we get: This is A 

Nota che sto effettivamente cercando di utilizzare la semantica C11 qui, ma sto usando g ++ come gcc non lo fa supporto C11 ancora.

Ora, per quanto posso vedere, guardando sia le specifiche C11 sia le specifiche C++ (C++ 11 e le specifiche precedenti - la semantica di globalità in linea e statica non sembra essere cambiata), dovrebbe fare quello che voglio, e l'errore quando si utilizza -O0 è un bug in gcc.

È questo corretto, o c'è qualcosa da qualche parte nelle specifiche che mi manca che renderebbe questo comportamento indefinito?

Modifica

La risposta comune sembra sostenere che fn deve essere dichiarato come static per questo lavoro. Ma secondo 6.7.4.6 delle specifiche C99 (6.7.4.7 nella specifica C11 - non sono sicuro delle specifiche C++):

Se tutte le dichiarazioni di ambito di file per una funzione in un'unità di traduzione includono funzione inline specificatore senza extern, quindi la definizione in quell'unità di traduzione è una definizione in linea . Una definizione in linea non fornisce una definizione esterna per la funzione, e non vieta una definizione esterna in un'altra unità di traduzione.

Quindi, poiché non c'è un esplicito extern qui, queste dovrebbero essere due funzioni inline indipendenti senza interazione l'una con l'altra. Non è richiesto esplicito static.

L'utilizzo di uno statico esplicito risolve il problema per C, ma non funziona per le funzioni membro inline C++, poiché la parola chiave static ha un significato completamente diverso in quel caso.

+0

Fuori interesse, il comportamento viene replicato se si sostituisce l'include con la definizione della funzione in linea? Se il preprocessore funziona come un primo passaggio indipendente come immagino, dovresti essere in grado di semplificare il test case. –

+0

Btw: quale versione di gcc? –

+0

gcc 4.5.3 e 4.6.1 si comportano entrambi in questo modo. –

risposta

8

Hai violato la regola di una definizione. La funzione non statica fn viene definita in modo diverso nelle due unità di traduzione. Uno si lega alla variabile id definita in a.cc, dove l'altro si lega con la variabile id in b.cc. Le definizioni sono testualmente identici, ma non è sufficiente a soddisfare la regola di una definizione, anche con l'eccezione di cui per le funzioni dichiarate inline, in modo da ottenere un comportamento indefinito.

Si sta utilizzando un compilatore C++, non un compilatore C, quindi qualsiasi cosa C11 dica è irrilevante per quanto riguarda il comportamento del programma C++. In C++ 11, lo standard (a § 3,2/5) sembra affermare la regola su come fn è permesso di fare riferimento a id (enfasi e ellissi mio):

Non ci può essere più di una definizione di un & hellip; funzione inline con collegamento esterno (7.1.2) & hellip; in un programma a condizione che ciascuna definizione venga visualizzata in un'unità di traduzione diversa, e che le definizioni soddisfino i seguenti requisiti. Dato tale entità denominata D definita in più di un'unità di traduzione, allora

  • ciascuna definizione di D comprende la stessa sequenza di token; e
  • in ogni definizione di D, nomi corrispondenti, sembrava fino secondo 3.4, farà riferimento a un'entità definita all'interno del definizione di D, o si riferiscono alla stessa entità, dopo la risoluzione di sovraccarico (13.3) e dopo l'abbinamento della specializzazione parziale modello (14.8.3), con la differenza che un nome può riferirsi ad un oggetto- con collegamento interno o assente se l'oggetto ha lo stesso tipo letterale in tutte le definizioni di D e l'oggetto è inizializzato con un'espressione costante (5.19) e il valore (ma non l'indirizzo) dello 012 Viene utilizzato l'oggettoe l'oggetto ha lo stesso valore in tutte le definizioni di D; e
  • & hellip;

Le definizioni di fn consistono nella stessa sequenza di token, ma si riferiscono a id, che non è definito all'interno D, non è la stessa entità in entrambe le unità di traduzione, e non ha lo stesso valore in tutte le definizioni. Non vedo alcuna disposizione nello standard C++ per una funzione inline che acquisisca implicitamente il collegamento interno. C++ 11 § 7.1.1/7 dice questo:

Un nome dichiarato in un ambito namespace senza classe di archiviazione-specificatore ha linkage esterno se non ha il collegamento interno a causa di una precedente dichiarazione e disponibile non è dichiarato const.

Se avete ottenuto il vostro comportamento previsto a certi livelli di ottimizzazione, o da alcune versioni di alcuni compilatori, dunque siete solo ottenere la versione particolarmente nefasto di un comportamento indefinito, dove le cose sembrano funzionare nonostante sia sbagliato.

+0

Ma secondo le specifiche, le due unità di compilazione forniscono due definizioni interne indipendenti della funzione, nessuna delle quali dovrebbe essere visibile esternamente e che non dovrebbe interagire in alcun modo ... –

+0

@ChrisDodd Fornisce due definizioni indipendenti della funzione , __anche di questi __ sono esternamente visibili e il linker è libero di lamentarsi rumorosamente di definizioni duplicate, o, come sembra aver fatto questo caso, scartare in silenzio uno, assumendo che siano identici (anche se non lo sono). Dichiarare 'fn()' come 'statico' li renderebbe interni non visibili esternamente. – twalberg

+0

@twalberg: vedere le specifiche sopra: "non fornisce una definizione esterna della funzione" –

3

Perché fn() non è dichiarato static, come sottolinea @RobKennedy, ci si avventura in terra UB. In questo caso specifico, -O0 probabilmente disabilita l'inlining, il che significa che una delle versioni non inline della funzione emesse verrà mantenuta e l'altra scartata, e entrambe le chiamate saranno relative a quella versione non inline. Se la versione A viene sempre mantenuta può dipendere dall'ordine in cui si specificano i file sulla riga di comando o da un numero qualsiasi di altre cose.-O1 probabilmente include l'inlining, nel qual caso, anche se c'è una copia non inline della funzione emessa, le due chiamate potrebbero essere ancora in linea, il che fornisce i risultati (erroneamente) previsti.

Problemi correlati