Beispiel für einen minimalen lauffähigen Bereich mit mehreren Dateien
Hier veranschauliche ich, wie static
beeinflusst den Geltungsbereich von Funktionsdefinitionen über mehrere Dateien hinweg.
a.c
#include <stdio.h>
/* Undefined behavior: already defined in main.
* Binutils 2.24 gives an error and refuses to link.
* https://stackoverflow.com/questions/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();
}
main.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 vorgelagert .
Kompilieren und ausführen:
gcc -c a.c -o a.o
gcc -c main.c -o main.o
gcc -o main main.o a.o
./main
Ausgabe:
main f
main sf
main f
a sf
Auslegung
- Es gibt zwei getrennte Funktionen
sf
eine für jede Datei
- es gibt eine einzige gemeinsame Funktion
f
Wie üblich gilt: Je kleiner der Umfang, desto besser, also immer Funktionen deklarieren static
wenn Sie können.
In der C-Programmierung werden häufig Dateien zur Darstellung von "Klassen" verwendet, und static
Funktionen stellen "private" Methoden der Klasse dar.
Ein gängiges C-Muster ist die Übergabe einer this
struct als erstes "Methoden"-Argument, was im Grunde das ist, was C++ unter der Haube tut.
Was die Normen dazu sagen
C99 N1256 Entwurf 6.7.1 "Speicherklassenspezifizierer" besagt, dass static
ist ein "Speicherklassenbezeichner".
6.2.2/3 "Verknüpfungen von Identifikatoren" sagt static
impliziert internal linkage
:
Wenn die Deklaration eines Dateibereichsbezeichners für ein Objekt oder eine Funktion den Speicherklassenspezifizierer static enthält, hat der Bezeichner eine interne Verknüpfung.
und 6.2.2/2 besagt, dass internal linkage
verhält sich wie in unserem Beispiel:
In der Menge der Übersetzungseinheiten und Bibliotheken, die ein ganzes Programm bilden, bezeichnet jede Deklaration eines bestimmten Bezeichners mit externer Verknüpfung dasselbe Objekt oder dieselbe Funktion. Innerhalb einer Übersetzungseinheit bezeichnet jede Deklaration eines Bezeichners mit interner Verknüpfung dasselbe Objekt oder dieselbe Funktion.
wobei "Übersetzungseinheit" eine Quelldatei nach der Vorverarbeitung ist.
Wie implementiert GCC es für ELF (Linux)?
Mit dem STB_LOCAL
Bindung.
Wenn wir kompilieren:
int f() { return 0; }
static int sf() { return 0; }
und demontieren Sie die Symboltabelle mit:
readelf -s main.o
die Ausgabe enthält:
Num: Value Size Type Bind Vis Ndx Name
5: 000000000000000b 11 FUNC LOCAL DEFAULT 1 sf
9: 0000000000000000 11 FUNC GLOBAL DEFAULT 1 f
Die Bindung ist also der einzige wesentliche Unterschied zwischen ihnen. Value
ist nur ihr Offset in der .bss
Abschnitt, so dass wir davon ausgehen, dass er sich unterscheiden wird.
STB_LOCAL
ist in der ELF-Spezifikation dokumentiert unter http://www.sco.com/developers/gabi/2003-12-17/ch4.symtab.html :
STB_LOCAL Lokale Symbole sind außerhalb der Objektdatei, die ihre Definition enthält, nicht sichtbar. Lokale Symbole desselben Namens können in mehreren Dateien existieren, ohne sich gegenseitig zu behindern
was sie zu einer perfekten Wahl für die Darstellung von static
.
Funktionen ohne Statik sind STB_GLOBAL
und in der Spezifikation steht:
Wenn der Link-Editor mehrere verschiebbare Objektdateien kombiniert, lässt er keine Mehrfachdefinitionen von STB_GLOBAL-Symbolen mit demselben Namen zu.
was mit den Verknüpfungsfehlern bei mehreren nicht statischen Definitionen kohärent ist.
Wenn wir die Optimierung mit -O3
die sf
Symbol wird komplett aus der Symboltabelle entfernt: es kann von außen sowieso nicht verwendet werden. TODO Warum sollten statische Funktionen überhaupt in der Symboltabelle verbleiben, wenn es keine Optimierung gibt? Können sie für irgendetwas verwendet werden?
Siehe auch
C++ anonyme Namespaces
In C++ können Sie anonyme Namespaces anstelle von statischen Namespaces verwenden, wodurch ein ähnlicher Effekt erzielt wird, die Typdefinitionen aber noch weiter versteckt werden: Unbenannte/anonyme Namensräume vs. statische Funktionen