Przykład minimalnego zakresu wielu plików do uruchomienia
Tutaj ilustruję, jak static
wpływa na zakres definicji funkcji w wielu plikach.
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();
}
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 w górę .
Skompiluj i uruchom:
gcc -c a.c -o a.o
gcc -c main.c -o main.o
gcc -o main main.o a.o
./main
Wynik:
main f
main sf
main f
a sf
Interpretacja
- istnieją dwie osobne funkcje
sf
, po jednej dla każdego pliku
- jest jedna wspólna funkcja
f
Jak zwykle, im mniejszy zakres, tym lepiej, więc zawsze deklaruj funkcje, static
jeśli możesz.
W programowaniu C pliki są często używane do reprezentowania „klas”, a static
funkcje reprezentują „prywatne” metody klasy.
Powszechnym wzorcem C jest przekazywanie this
struktury jako pierwszego argumentu „metody”, który jest w zasadzie tym, co C ++ robi pod maską.
Co mówią o tym standardy
C99 N1256 wersja robocza 6.7.1 „ Specyfikatory klasy pamięci” mówią, że static
jest to „specyfikator klasy pamięci”.
6.2.2 / 3 „Powiązania identyfikatorów” mówi static
zakłada internal linkage
:
Jeśli deklaracja identyfikatora zakresu pliku dla obiektu lub funkcji zawiera statyczny specyfikator klasy pamięci, identyfikator ma wewnętrzne powiązanie.
a 6.2.2 / 2 mówi, że internal linkage
zachowuje się jak w naszym przykładzie:
W zestawie jednostek tłumaczeniowych i bibliotek, które stanowią cały program, każda deklaracja określonego identyfikatora z zewnętrznym powiązaniem oznacza ten sam obiekt lub funkcję. W obrębie jednej jednostki tłumaczeniowej każda deklaracja identyfikatora z wewnętrznym powiązaniem oznacza ten sam obiekt lub funkcję.
gdzie „jednostka tłumacząca” jest plikiem źródłowym po wstępnym przetwarzaniu.
Jak GCC implementuje to dla ELF (Linux)?
Z STB_LOCAL
wiązaniem.
Jeśli skompilujemy:
int f() { return 0; }
static int sf() { return 0; }
i zdemontować tabelę symboli za pomocą:
readelf -s main.o
wyjście zawiera:
Num: Value Size Type Bind Vis Ndx Name
5: 000000000000000b 11 FUNC LOCAL DEFAULT 1 sf
9: 0000000000000000 11 FUNC GLOBAL DEFAULT 1 f
więc wiązanie jest jedyną znaczącą różnicą między nimi. Value
jest tylko ich przesunięciem do .bss
sekcji, więc spodziewamy się, że będzie się różnić.
STB_LOCAL
jest udokumentowany w specyfikacji ELF pod adresem http://www.sco.com/developers/gabi/2003-12-17/ch4.symtab.html :
STB_LOCAL Symbole lokalne nie są widoczne poza plikiem obiektowym zawierającym ich definicję. Lokalne symbole o tej samej nazwie mogą istnieć w wielu plikach bez kolidowania ze sobą
co czyni go idealnym wyborem do reprezentowania static
.
Funkcje bez statyczne są STB_GLOBAL
, a specyfikacja mówi:
Gdy edytor łączy łączy kilka plików obiektów relokowalnych, nie pozwala na wiele definicji symboli STB_GLOBAL o tej samej nazwie.
co jest spójne z błędami łącza w wielu definicjach niestatycznych.
Jeśli zwiększymy optymalizację za pomocą -O3
, sf
symbol zostanie całkowicie usunięty z tabeli symboli: i tak nie można go używać z zewnątrz. TODO po co w ogóle utrzymywać funkcje statyczne na tablicy symboli, gdy nie ma optymalizacji? Czy można ich używać do czegokolwiek?
Zobacz też
Anonimowe przestrzenie nazw C ++
W C ++ możesz użyć anonimowych przestrzeni nazw zamiast statycznych, co daje podobny efekt, ale dodatkowo ukrywa definicje typów: Bezimienne / anonimowe przestrzenie nazw vs. funkcje statyczne