2013-02-22 4 views
7

Oggi mi sono imbattuto in un bug curioso che in qualche modo sono riuscito a evitare fino ad ora.Generazione di un errore nelle definizioni in conflitto con il collegamento in linea

file1.cpp:

#include <iostream> 
inline void print() { std::cout << "Print1\n"; } 
void y() { print(); } 

file2.cpp:

#include <iostream> 
inline void print() { std::cout << "Print2\n"; } 
void x() { print(); } 

main.cpp:

int x(); 
int y(); 

int main(){ 
    x(); 
    y(); 
} 

Uscita:

Print1       (Expected Print2) 
Print1 

Poiché print() ha linkage linea, questo produce nessun errore definizione multipla (compilato con g++ -Wall file1.cpp file2.cpp main.cpp) e il simbolo duplicato è silenziosamente collassato. Il caso reale in cui l'ho visto era con metodi di classe inline, non esplicite funzioni inline, ma l'effetto è lo stesso.

Mi chiedo se è disponibile un'opzione di collegamento o simile che consentirà di generare un avviso quando viene eseguito questo tipo di errore?

+0

Non credo che stamperà 'Print1 Print1'. Dovrebbe anche stampare 'Print2'. – Nawaz

risposta

0

sto per spudoratezza copia-incolla da un thread di posta elettronica ho trovato here su un argomento simile ...

Questo non è un problema di gcc.

Non hai menzionato il sistema operativo in uso. Supporrò che sia GNU/Linux. Su GNU/Linux, o su qualsiasi altro sistema basato su ELF, c'è uno spazio dei nomi unico globale di variabili e funzioni globali. Quando due librerie condivise utilizzano lo stesso nome di variabile globale, si riferiscono a alla stessa variabile. Questa è una caratteristica.

Se si desidera che qualcosa di diverso accada, esaminare la visibilità dei simboli e gli script di versione del linker.

Sembrerebbe che dovresti essere in grado di dire al linker/compilatore che il simbolo X dovrebbe essere unico e lamentarsi se non lo è.

2

non è necessario/obbligatorio che un errore/avviso venga generato, dallo standard. Si tratta di una violazione di One Definition Rule di C++ a causa del fatto di avere corpi di funzioni diversi.

Citando: Extern Inlines by Default

Il significato di "inline extern" è che se chiamate a una funzione non sono generati in linea, poi un compilatore dovrebbe rendere una sola copia del definizione della funzione, da condividere su tutti i file oggetto.

Se quanto sopra si verifica, il comportamento di quel programma è considerato non definito secondo lo standard del linguaggio, ma che né il compilatore né il linker è tenuto a dare un messaggio di diagnostica. In pratica, questo significa che, a seconda di come funziona l'implementazione, il compilatore o il linker può semplicemente selezionare una delle definizioni da utilizzare ovunque.

Il comportamento è coerente con la definizione di GCC di vague linkage qui.

+0

Grazie. Guardando indietro avrei dovuto formulare meglio la domanda, ma per chiarire - mi rendo conto che il programma è in violazione e che il comportamento di gcc è corretto - mi interessa solo (presumibilmente toolchain specifici) i modi per rilevarlo al momento della compilazione. – jmetcalfe

2

Le funzioni non sono collegamenti interni o in uno spazio dei nomi anonimo, quindi sono visibilmente esterne con lo stesso nome. Hanno corpi diversi, quindi hai chiaramente violato l'unica regola di definizione. A quel punto qualsiasi speculazione su ciò che accadrà non è utile in quanto il tuo programma è malformato.

Immagino che tu abbia compilato senza ottimizzazione e che il compilatore abbia generato chiamate di funzione invece di inlining e ha scelto uno dei corpi da usare come funzione da chiamare (lasciando l'altro orfano). Ora presumibilmente se si è compilato con l'ottimizzazione, il risultato atteso verrà emesso ma il programma sarà ancora errato.

MODIFICA per il commento: Sfortunatamente non è necessario che i compilatori diagnostichino le violazioni della regola di una definizione (e potrebbero non essere nemmeno in grado di rilevare tutti i casi). Tuttavia ci sono alcune cose che puoi fare: funzioni di sorgente-privato

  • Effettuare sempre o static o in uno spazio dei nomi anonima (io preferisco lo spazio dei nomi per scopi raggruppamento logico ma o va bene).
  • Prestare particolare attenzione ai metodi inline (indipendentemente dalla posizione) poiché indicano esplicitamente al compilatore che tutte le versioni saranno identiche (per i metodi non inline è probabile che almeno si verifichi un errore di simbolo duplicato dal linker). L'approccio più sicuro qui è quello di evitare funzioni inline del namespace globale (o prestare particolare attenzione alla denominazione). Inoltre, devi essere molto attento a modificare #define s non modificare il corpo della funzione (ad esempio utilizzando assert in una funzione inline in cui uno utilizza ha NDEBUG e l'altro non).
  • Utilizzare logicamente classi e spazi dei nomi per suddividere il codice e impedire la definizione di più definizioni dello stesso simbolo.
+0

Sì, questo era senza ottimizzazione. Immagino che sarebbe come hai descritto con l'ottimizzazione abilitata (almeno con queste piccole funzioni). Mi rendo conto che il programma è malformato e che il comportamento di gcc è corretto, interessato solo ai modi di rilevare/prevenire questo. – jmetcalfe

-2

Per rendere locale una funzione in un file .cpp (o .c), è necessario impostarla come static. Quando hai dichiarato la stampa in due file .cpp separati, entrambi compilati in print_v, il secondo simbolo è stato rilasciato (ignorato) * dal compilatore.

Questa è solo un'ipotesi di come ha funzionato sulla mia macchina. Ho provato lo stesso codice su un Mac OS X Mountain Lion e ho ottenuto lo stesso risultato, ma sono riuscito a risolverlo aggiungendo static a entrambe le funzioni.

* se si modifica l'ordine dei file nella compilazione, il comportamento cambierà.provare g++ file{2,1}.cpp main.cpp e l'uscita dovrebbe essere

print2

print2

Cordiali saluti, Abdulrahman Alotaibi