Je sais qu'il existe théoriquement un autre niveau d'indirection lors de leur accès,
Ça dépend, un compilateur vraiment stupide peut introduire des niveaux supplémentaires d'indirection pour une structure, mais un compilateur intelligent peut en fait produire un meilleur code si une structure est impliquée.
Contrairement à d'autres réponses, le compilateur NE connaît PAS l'adresse des variables globales. Pour les variables globales dans la même unité de compilation, il connaît leur emplacement les uns par rapport aux autres, mais pas leur emplacement absolu. Pour les variables d'autres unités de compilation, il ne sait rien de leur emplacement en termes absolus ou relatifs.
Au lieu de cela, le code généré par le compilateur aura des espaces réservés pour les emplacements des variables globales, ceux-ci seront ensuite remplacés par des emplacements réels lorsque l'adresse finale est déterminée (traditionnellement lorsque le programme est lié, mais parfois pas avant son exécution ).
De plus, bien que certains processeurs (comme le 6502) puissent accéder directement à un emplacement mémoire global sans aucune configuration préalable, il y en a beaucoup qui ne le peuvent pas. Sur arm par exemple, pour accéder à une variable globale, le compilateur doit d'abord charger l'adresse de la variable dans un registre, soit en utilisant un pool littéral, soit, sur les versions plus récentes d'arm, en utilisant movw / movt. Accédez ensuite à la variable globale en utilisant une instruction de chargement relative au registre.
Cela signifie que pour les variables en dehors de l'unité de compilation, l'accès à plusieurs éléments de la même structure globale est probablement plus efficace que l'accès à des variables globales individuelles.
Pour tester cela, j'ai introduit le code suivant dans godbolt avec l'optimisation ARM gcc 8.2 et -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) {
toto.a = 1;
toto.b = 2;
}
Cela a abouti à
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
Dans le cas de f1, nous voyons que le compilateur charge les adresses de a et b séparément des pools littéraux (les adresses dans les pools littéraux seront remplies plus tard par l'éditeur de liens).
Cependant dans le cas de f2 le compilateur n'a qu'à charger l'adresse de la structure du pool littéral une seule fois, mieux encore car il sait que les deux variables sont côte à côte en mémoire il peut les écrire avec un seul "storeplusieurs "instructions plutôt que deux instructions de magasin séparées.
mais à partir d'un style de POV, est-ce mieux ou pire que de les mettre dans des fichiers globals.c et / ou globals.h?
IMO qui dépend si les variables sont réellement liées ou non.