2015-03-17 15 views
6

Attualmente sto aggiornando una libreria C++ per Arduino (in particolare i processori AVR a 8 bit compilati usando avr-gcc).Variabile extern solo nell'intestazione che funziona in modo imprevisto, perché?

In genere gli autori delle librerie Arduino predefinite amano includere una variabile extern per la classe all'interno dell'intestazione, che viene anche definita nel file class.cpp. Questo presumo fondamentalmente di avere tutto pronto per i principianti come oggetti built-in.

Lo scenario che ho è: La libreria che ho aggiornato non richiede più il file .cpp e l'ho rimosso dalla libreria. Non è stato fino a quando non sono andato su un passaggio finale a verificare i bug che ho realizzato, non è stato prodotto alcun errore di linker nonostante non fosse stata fornita una definizione per la variabile extern in un file .cpp.

Questo è semplice come posso farlo (file di intestazione):

struct Foo{ 
    void method() {} 
}; 

extern Foo foo; 

Compreso il codice ed il suo utilizzo in uno o più file di origine non causa alcun errore di linker. L'ho provato in entrambe le versioni di GCC che Arduino usa (4.3.7, 4.8.1) e con C++ 11 abilitato/disabilitato.

Nel mio tentativo di causare un errore, ho trovato che era possibile solo quando si faceva qualcosa come prendere l'indirizzo dell'oggetto o modificare il contenuto di una variabile fittizia che ho aggiunto.

Dopo aver scoperto questo trovo 'importante notare:

  • Le funzioni di classe restituiscono solo altri oggetti, come in, niente come gli operatori che tornano riferimenti a se stesso, o anche una copia.
  • Modifica solo oggetti esterni (registri che sono effettivamente riferimenti a volatile uint8_t nel codice) e restituisce provvisori di altre classi.
  • Tutte le funzioni di classe in questa intestazione sono così semplici che costano meno o uguale al costo di una chiamata di funzione, quindi sono (nei miei test) completamente allineate nel chiamante. Una tipica affermazione può creare molti oggetti temporanei nella catena di chiamata, tuttavia il compilatore vede attraverso questi e produce registri di modifica del codice efficienti direttamente, piuttosto che un insieme di chiamate di funzioni annidate.

Ricordo anche la lettura in n37977.1.1 - 8 che extern può essere utilizzato su tipi incompleti, ma la classe è completamente definito considerando che la dichiarazione non è (questo è probabilmente irrilevante).

Sono portato a credere che questo possa essere il risultato di ottimizzazioni in gioco. Ho visto l'effetto che prendere l'indirizzo ha su oggetti che altrimenti sarebbero considerati costanti e compilati senza l'utilizzo della RAM. Aggiungendo qualsiasi livello di riferimento indiretto a un oggetto in cui il compilatore non può garantire lo stato causerà questo comportamento di consumo della RAM.

Quindi, forse ho risposto alla mia domanda semplicemente chiedendole, tuttavia sto ancora facendo delle supposizioni e mi dà fastidio. Dopo un po 'di codifica C++ per hobby, letteralmente l'unica cosa nella mia lista di do-not's è fare ipotesi.

Davvero, quello che voglio sapere è:

  • Per quanto riguarda la soluzione di lavoro che ho, si tratta di un semplice caso di documentare l'impossibilità di prendere l'indirizzo (causa indiretto) della classe?
  • È solo un comportamento caso limite causato da ottimizzazioni che eliminano la necessità di collegare qualcosa?
  • O è un comportamento non definito semplice e semplice. Come in GCC potrebbe esserci un bug e consentire il codice che potrebbe fallire se le ottimizzazioni fossero ridotte o disabilitate?

Oppure uno di voi può avere la fortuna di essere in possesso di un anello decodificatore che può trovare un paragrafo adatto nello standard che delinea le specifiche.

Questa è la mia prima domanda qui, quindi fammi sapere se desideri conoscere alcuni dettagli, posso anche fornire link GitHub al codice se necessario.

Modifica: Poiché la libreria deve essere compatibile con il codice esistente, è necessario mantenere la possibilità di utilizzare la sintassi del punto, altrimenti avrei semplicemente una classe di funzioni statiche.

Per rimuovere le ipotesi, per ora, vedo due opzioni:

  • Aggiungi un cpp solo per la dichiarazione delle variabili.
  • Utilizzare una definizione nell'intestazione come #define foo (Foo()) consentendo la sintassi del punto tramite un temporaneo.

Preferisco il metodo utilizzando un definire, cosa pensa la comunità?

Cheers.

+0

Essere coerenti. Aggiungi il file .cpp solo per le definizioni esterne e commentalo correttamente. L'aggiunta di define renderà ulteriormente offuscato il codice. – SChepurin

risposta

1

È un comportamento di caso limite causato dall'ottimizzazione oppure non si utilizza mai la variabile foo nel codice. Non sono sicuro al 100% che non sia formalmente un comportamento indefinito, ma sono abbastanza sicuro che non sia indefinito dal punto di vista pratico.

extern le variabili sono implementate in tale modo, che il codice compilato con esse produce le cosiddette delocalizzazioni - luoghi vuoti in cui devono essere posizionati gli addr della variabile - che vengono poi riempiti dal linker. Apparentemente il codice foo non viene mai utilizzato nel codice in modo tale da richiedere il suo indirizzo e pertanto il linker non tenta nemmeno di trovare quel simbolo. Se disattivi l'ottimizzazione (-O0) probabilmente otterrai l'errore linker.

Aggiornamento: Se si desidera mantenere "dot notation", ma rimuovere il problema con extern non definita, è possibile sostituire con externstatic (nel file di intestazione), la creazione di "istanza" separata di variabile per ciascun TU. Poiché questa variabile verrà comunque ottimizzata, ciò non cambierà affatto il codice reale, ma funzionerà anche per la build non ottimizzata.

+0

Sì, l'uso di '-O0' ha causato l'errore del linker, che è quello che sospettavo nel mio post, ora confermato. Tuttavia, la mancanza di definizione non dovrebbe essere un errore indipendentemente dal modo in cui il compilatore può svolgere il proprio lavoro. Il suo comportamento diventa ambiguo in base alle ottimizzazioni piuttosto che all'utilizzo. –

+1

@ChrisA: non è richiesta alcuna diagnostica per le violazioni delle regole di una definizione, poiché ciò richiederebbe al linker, non al compilatore, di essere abbastanza intelligente da diagnosticarlo. Si ottiene un comportamento indefinito se si infrange la regola. –

+0

@ChrisA Il comportamento corrente può essere molto comodo quando si scrivono le librerie: è possibile creare intestazione comune per l'intera libreria, ma inserire il codice compilato in diversi file e collegare solo quelli effettivamente utilizzati. – Frax

2

La dichiarazione di qualcosa extern informa semplicemente l'assemblatore e il linker che ogni volta che si utilizza quell'etichetta/simbolo, dovrebbe fare riferimento alla voce nella tabella dei simboli, anziché a un simbolo assegnato localmente.

Il ruolo del linker è sostituire le voci della tabella dei simboli con un riferimento effettivo allo spazio indirizzo, quando possibile.

Se non si utilizza affatto il simbolo nel file C, non verrà visualizzato nel codice assembly e, pertanto, non causerà alcun errore del linker quando il modulo è collegato ad altri, poiché non vi è alcun indefinito riferimento.