So che teoricamente esiste un altro livello di riferimento indiretto quando si accede ad essi,
Beh, dipende, un compilatore veramente stupido può introdurre livelli extra di riferimento indiretto per una struttura, ma un compilatore intelligente può effettivamente produrre codice migliore se è coinvolta una struttura.
Contrariamente ad altre risposte, il compilatore NON conosce l'indirizzo delle variabili globali. Per le variabili globali nella stessa unità di compilazione conosce la loro posizione l'una rispetto all'altra, ma non la loro posizione assoluta. Per le variabili in altre unità di compilazione non sa nulla della loro posizione in termini assoluti o relativi.
Invece il codice generato dal compilatore avrà dei segnaposto per le posizioni delle variabili globali, queste saranno poi sostituite dalle posizioni effettive quando viene determinato l'indirizzo finale (tradizionalmente quando il programma è collegato, ma a volte non finché non viene eseguito ).
Inoltre, mentre ci sono alcune CPU (come il 6502) che possono accedere direttamente a una posizione di memoria globale senza alcuna configurazione precedente, ce ne sono molte che non possono. Ad esempio, su arm, per accedere a una variabile globale, il compilatore deve in genere caricare prima l'indirizzo della variabile in un registro, utilizzando un pool letterale o, nelle versioni più recenti di arm, utilizzando movw / movt. Quindi accedi alla variabile globale utilizzando un'istruzione di caricamento relativa al registro.
Ciò significa che per variabili esterne all'unità di compilazione l'accesso a più elementi della stessa struttura globale è probabilmente più efficiente rispetto all'accesso a singole variabili globali.
Per testarlo ho inserito il seguente codice in godbolt con ARM gcc 8.2 e ottimizzazione -O3.
extern int a;
extern int b;
struct Foo {
int a;
int b;
};
extern struct Foo foo;
void f1 (void) {
a = 1;
b = 2;
}
void f2 (void) {
foo.a = 1;
foo.b = 2;
}
Questo ha portato a
f1:
mov r0, # 1
mov r2, # 2
ldr r1, .L3
ldr r3, .L3 + 4
str r0, [r1]
str r2, [r3]
bx lr
.L3:
.word a
.word b
f2:
mov r1, # 1
mov r2, # 2
ldr r3, .L6
stm r3, {r1, r2}
bx lr
.L6:
.word foo
Nel caso di f1 vediamo che il compilatore carica gli indirizzi di aeb separatamente dai pool letterali (gli indirizzi nei pool letterali verranno inseriti successivamente dal linker).
Tuttavia nel caso di f2 il compilatore deve caricare solo una volta l'indirizzo della struttura dal pool letterale, meglio ancora perché sa che le due variabili sono una accanto all'altra in memoria può scriverle con un unico "archiviopiù "istruzioni" anziché due istruzioni separate per il negozio.
ma da uno stile POV è meglio o peggio che metterli nei file globals.c e / o globals.h?
IMO che dipende se le variabili sono effettivamente correlate o meno.