Exemple de portée multi-fichiers exécutable minimal
Ici, j'illustre comment static
affecte la portée des définitions de fonction sur plusieurs fichiers.
ac
#include <stdio.h>
/* Undefined behavior: already defined in main.
* Binutils 2.24 gives an error and refuses to link.
* /programming/27667277/why-does-borland-compile-with-multiple-definitions-of-same-object-in-different-c
*/
/*void f() { puts("a f"); }*/
/* OK: only declared, not defined. Will use the one in main. */
void f(void);
/* OK: only visible to this file. */
static void sf() { puts("a sf"); }
void a() {
f();
sf();
}
principal c
#include <stdio.h>
void a(void);
void f() { puts("main f"); }
static void sf() { puts("main sf"); }
void m() {
f();
sf();
}
int main() {
m();
a();
return 0;
}
GitHub en amont .
Compiler et exécuter:
gcc -c a.c -o a.o
gcc -c main.c -o main.o
gcc -o main main.o a.o
./main
Production:
main f
main sf
main f
a sf
Interprétation
- il y a deux fonctions distinctes
sf
, une pour chaque fichier
- il y a une seule fonction partagée
f
Comme d'habitude, plus la portée est petite, mieux c'est, alors déclarez toujours les fonctions static
si vous le pouvez.
En programmation C, les fichiers sont souvent utilisés pour représenter des "classes" et les static
fonctions représentent des méthodes "privées" de la classe.
Un modèle C commun consiste à passer une this
structure comme premier argument de "méthode", ce qui est essentiellement ce que fait C ++ sous le capot.
Ce que les normes en disent
C99 N1256 draft 6.7.1 "Storage-class specifiers" dit qu'il static
s'agit d'un "stockage-class specifier".
6.2.2 / 3 "Liens d'identifiants" dit static
implique internal linkage
:
Si la déclaration d'un identificateur de portée de fichier pour un objet ou une fonction contient le spécificateur de classe de stockage statique, l'identificateur a une liaison interne.
et 6.2.2 / 2 dit que internal linkage
se comporte comme dans notre exemple:
Dans l'ensemble d'unités de traduction et de bibliothèques qui constitue un programme entier, chaque déclaration d'un identifiant particulier avec liaison externe désigne le même objet ou la même fonction. Au sein d'une unité de traduction, chaque déclaration d'un identifiant avec lien interne dénote le même objet ou fonction.
où "unité de traduction" est un fichier source après le prétraitement.
Comment GCC l'implémente-t-il pour ELF (Linux)?
Avec la STB_LOCAL
reliure.
Si nous compilons:
int f() { return 0; }
static int sf() { return 0; }
et démontez la table des symboles avec:
readelf -s main.o
la sortie contient:
Num: Value Size Type Bind Vis Ndx Name
5: 000000000000000b 11 FUNC LOCAL DEFAULT 1 sf
9: 0000000000000000 11 FUNC GLOBAL DEFAULT 1 f
donc la liaison est la seule différence significative entre eux. Value
est juste leur décalage dans la .bss
section, nous nous attendons donc à ce qu'il diffère.
STB_LOCAL
est documenté sur la spécification ELF à http://www.sco.com/developers/gabi/2003-12-17/ch4.symtab.html :
STB_LOCAL Les symboles locaux ne sont pas visibles en dehors du fichier objet contenant leur définition. Des symboles locaux du même nom peuvent exister dans plusieurs fichiers sans interférer les uns avec les autres
ce qui en fait un choix parfait pour représenter static
.
Les fonctions sans statique sont STB_GLOBAL
, et la spécification dit:
Lorsque l'éditeur de liens combine plusieurs fichiers d'objets déplaçables, il n'autorise pas plusieurs définitions de symboles STB_GLOBAL avec le même nom.
ce qui est cohérent avec les erreurs de liaison sur plusieurs définitions non statiques.
Si nous augmentons l'optimisation avec -O3
, le sf
symbole est entièrement supprimé de la table des symboles: il ne peut en aucun cas être utilisé de l'extérieur. TODO pourquoi garder les fonctions statiques sur la table des symboles quand il n'y a pas d'optimisation? Peuvent-ils être utilisés pour quelque chose?
Voir également
Espaces de noms anonymes C ++
En C ++, vous souhaiterez peut-être utiliser des espaces de noms anonymes au lieu de statiques, ce qui produit un effet similaire, mais masque davantage les définitions de type: espaces de noms sans nom / anonymes vs fonctions statiques