2013-03-22 6 views
11

Sto sperimentando la visibilità dei simboli C++ su Linux e gcc. Sembra che il modo preferito sia usare -fvisibility = hidden e esportare i simboli usati uno per uno in base alla pagina wiki di gcc Visibility (http://gcc.gnu.org/wiki/Visibility). Il mio problema è che molte librerie non gestiscono bene questo, si dimenticano di esportare esplicitamente simboli, che è un problema serio. Dopo diversi bug risolti, anche alcune parti di boost potrebbero essere influenzate. Ovviamente questi bug dovrebbero essere corretti, ma fino a quel momento mi piacerebbe usare un modo "sicuro" per nascondere il più possibile i simboli.Visibilità dei simboli e spazio dei nomi

Mi è venuta una soluzione: inserisco tutti i simboli in un namespace e utilizzo l'attributo symbol hide su questo ed esporta l'interfaccia pubblica, in questo modo solo i miei simboli possono essere influenzati.

Il problema è che ho ricevuto un messaggio di avviso quando compilo qualcosa contro quella libreria per ogni classe che non ho esportato e che utilizzo nell'applicazione come campo della classe.

namespace MyDSO __attribute__ ((visibility ("hidden"))) { 
    struct Foo { 
    void bar() __attribute__ ((visibility ("default"))) {} 
    }; 
} 

struct Bar { 
    MyDSO::Foo foo; 
}; 

int main() {} 

Il messaggio di avviso possono essere riprodotti in questo piccolo esempio, ma naturalmente lo spazio dei nomi dovrebbe essere in una libreria l'altra classe nell'applicazione.

$ gcc-4.7.1 namespace.cpp -o namespace 
namespace.cpp:7:8: warning: ‘Bar’ declared with greater visibility than the type of its field ‘Bar::foo’ [-Wattributes] 

quanto ho capito visibilità simbolo, nascondendo namespace dovrebbe avere effetto del tutto simile all'uso -fvisibility = nascosto, ma non ho mai avuto avvertimenti simili utilizzando quest'ultimo. Vedo che quando passo -fvisibility = nascosto all'applicazione anche la classe nell'applicazione sarà nascosta, quindi non avrò un avvertimento. Ma quando non passo l'opzione nessuno dei simboli nelle intestazioni sembrerà nascosto al compilatore, quindi non avrò più un avviso.

Qual è la proposta di questo messaggio di avviso? È un problema serio? In quali situazioni questo può causare problemi? Come nascondere lo spazio dei nomi è diverso da fvisibility = hidden?

risposta

16

Prima di rispondere alla tua domanda specifica, dovrei menzionare per altri che l'applicazione degli attributi di visibilità dei simboli per spazio dei nomi è una funzione specifica di GCC. MSVC supporta solo dllexport su classi, funzioni e variabili e, se si desidera che il codice sia portatile, è necessario che corrisponda a MSVC. Come indica la mia guida alla visibilità del simbolo GCC originale (quella che hai collegato al sito Web di GCC), i macchinari dllexport basati su macro di MSVC possono essere facilmente riutilizzati per ottenere qualcosa di simile su GCC, quindi il porting su MSVC ti consentirà di visualizzare la visibilità dei simboli "gratuitamente" ".

Per quanto riguarda il problema specifico, GCC è corretto per avvisare. Se un utente esterno ha provato a utilizzare la barra pubblica di tipo ha quasi certamente bisogno di utilizzare tutto all'interno della barra, tra cui Bar :: pippo. Per la stessa identica ragione, tutte le funzioni dei membri privati, nonostante siano private, devono essere visibili. Molte persone ne sono sorprese, ragionando sul fatto che i simboli delle funzioni dei membri privati ​​sono per definizione inaccessibili a chiunque, ma dimenticano che solo perché il programmatore non ha accesso non significa che il compilatore non sia necessario accesso. In altre parole, le funzioni dei membri privati ​​sono private per te, ma non il compilatore. Se compaiono in un file di intestazione, ciò significa in genere che il compilatore ha bisogno di accesso, anche in uno spazio dei nomi anonimo (che è solo anonimo ai programmatori, non ai compilatori che tendono ad usare un hash dei contenuti come nome dello spazio dei nomi "reale").

Nascondere uno spazio dei nomi ha effetti molto diversi da -fvisibility = hidden. Questo perché GCC diffonde molti simboli al di sopra e al di là di quelli per un tipo specifico, ad es. per vtables, per type_info ecc. -fvisibility = nasconde cose nascoste che non puoi nascondere da alcun compilatore in modo istruito, ed è assolutamente essenziale caricare due binari nello stesso processo con simboli collidenti, ad es.due oggetti condivisi creati utilizzando diverse versioni di Boost.

Apprezzo i tuoi tentativi di risolvere i problemi causati dalla visibilità dei simboli danneggiati nell'ELF e le conseguenze sui file binari C++ danneggiati e la produttività del programmatore molto persa. Tuttavia non è possibile correggerli: sono errori in ELF stesso, che è stato progettato per C e non C++. Se c'è qualche consolazione, ho scritto un white paper interno di BlackBerry alcuni mesi fa su questo argomento, poiché i problemi di visibilità dei simboli ELF sono un problema altrettanto per noi in BB10, quanto lo sono per qualsiasi grande azienda con una significativa base di codice C++. Quindi, forse potresti vedere alcune soluzioni proposte per C++ 17, specialmente se l'implementazione dei moduli C++ di Doug Gregor fa buoni progressi.

+0

Grazie per la tua risposta dettagliata. Solo per curiosità, mi interessa quali sono i simboli necessari al compilatore che non possono essere generati? Per quanto ne sappia nelle classi banali anche il typeinfo può essere generato. Quando si utilizza vis = hidden non si ottiene un avviso anche quando si nascondono simboli che non devono essere nascosti, si ottiene un errore di simbolo non definito dal linker. Usando lo spazio dei nomi nascosto gcc può rilevare il problema. Forse c'è un uso legittimo di esportare solo alcuni simboli in una classe, ma gcc emette comunque un avviso. I moduli C++ di Doug Gregor sono molto interessanti, mi è piaciuta la sua presentazione, grazie per averlo condiviso. – VargaD

+1

In breve, digitareinfo per qualsiasi classe con un virtual viene sempre emesso, mentre typeinfo per le classi senza viene emesso solo quando typeid() o qualcosa che lo utilizza (eccezioni catch, dynamic_cast <> etc) viene utilizzato. Inoltre, la maggior parte dei compilatori emette due o più implementazioni del costruttore per ogni programma di costruzione specificato, e c'è anche della magia nella generazione del distruttore. In breve, -fvisibility = hidden nasconde il lotto, e il tuo metodo nasconderebbe solo le cose specificate dal programmatore e non gli interni magici. Molto di questo comincia a diventare più semplice con i moduli C++, anche se non è affatto risolto. Niall –

0

L'utilizzo degli attributi di visibilità sembra a ritroso; Penso che si otterrebbero risultati migliori usando -fvisibility = hidden e aggiungendo visibilità "default" allo spazio dei nomi di dichiarazione della libreria, poiché l'interfaccia della libreria presumibilmente ha una visibilità predefinita o non è possibile utilizzarla dall'applicazione. Se non vuoi modificare le intestazioni della libreria, puoi usare #pragma GCC visibility push/pop attorno al tuo #include.

Inoltre, come afferma Niall, la marcatura delle singole funzioni membro come predefinite non funziona, l'intero tipo Foo deve avere visibilità predefinita se fa parte dell'interfaccia della libreria.

+0

Grazie per la risposta. Non vedo perché l'intera classe debba essere esportata. La maggior parte delle classi non ha nemmeno un vtable. Ho fatto alcune applicazioni di test, ho persino provato a lanciare l'eccezione non esportata, ma tutte hanno funzionato abbastanza bene. Cerco di capire perché deve essere esportato. Sai in quali circostanze fallisce? Puoi mostrare un esempio? – VargaD

+0

Il problema di base è che in C++ le classi hanno il collegamento; se una classe ha visibilità nascosta, significa che non hai intenzione di usarla al di fuori del proprio DSO. E quindi dare a una funzione membro una visibilità maggiore della sua classe non ha senso, dal momento che è necessario utilizzare la classe per utilizzare la funzione membro. Perché non vuoi esportare la classe stessa? –

+0

Il mio problema è che l'esportazione dell'intera classe non è gestibile, i simboli vengono esportati facilmente. In C usando -fvisibility-hidden hai appena esportato il simbolo che vuoi esportare, ma in C++ devi esportare l'intera classe e difficile nascondere tutti i simboli che non vuoi esportare. Vedo che le classi che hanno vtable devono essere esportate. Ho letto molte volte che in C++ la visibilità dei simboli può causare un sacco di problemi, e non dovrei nascondere classi che hanno metodi esportati, ma non riesco a capire perché. Ho provato a produrre errori di runtime a causa della visibilità, ma non ci sono riuscito. – VargaD