Utworzenie funkcji staticukrywa ją przed innymi jednostkami tłumaczeniowymi, co pomaga zapewnić hermetyzację .
helper_file.c
int f1(int);/* prototype */staticint f2(int);/* prototype */int f1(int foo){return f2(foo);/* ok, f2 is in the same translation unit *//* (basically same .c file) as f1 */}int f2(int foo){return42+ foo;}
main.c :
int f1(int);/* prototype */int f2(int);/* prototype */int main(void){
f1(10);/* ok, f1 is visible to the linker */
f2(12);/* nope, f2 is not visible to the linker */return0;}
Czy jednostka tłumaczeniowa to właściwa terminologia do użycia w tym miejscu? Czy plik obiektu nie byłby dokładniejszy? Z tego, co rozumiem, funkcja statyczna jest ukryta przed konsolidatorem, a konsolidator nie działa na jednostkach tłumaczeniowych.
Steven Eckhoff
2
Powinienem był również powiedzieć, że lubię myśleć o tym jako o ukryciu przed linkerem; wydaje się to jaśniejsze w ten sposób.
Steven Eckhoff
1
tak więc, funkcja wewnętrzna (której na pewno nie będziemy wywoływać jej poza plikiem c), powinniśmy umieścić ją jako funkcję statyczną, prawda? Więc możemy być pewni, że nie może zadzwonić gdzie indziej. Dzięki :)
hqt
1
Jak to skompilujesz? Czy używasz #include <helper_file.c>? Myślę, że wtedy byłaby to pojedyncza jednostka tłumaczeniowa ...
Atcold
2
@Atcold: sposób, w jaki napisałem kod, po prostu dołączasz 2 pliki źródłowe w linii poleceń, w ten sposób gcc -std=c99 -pedantic -Wall -Wextra main.c helper_file.c. Prototypy funkcji znajdują się w obu plikach źródłowych (nie ma potrzeby plików nagłówkowych). Konsolidator rozwiąże funkcje.
pmg
80
pmg mówi o hermetyzacji; poza ukryciem funkcji przed innymi jednostkami tłumaczeniowymi (a raczej z tego powodu ), tworzenie funkcji staticmoże również przynosić korzyści w zakresie wydajności w obecności optymalizacji kompilatora.
Ponieważ staticfunkcji nie można wywołać z dowolnego miejsca spoza bieżącej jednostki tłumaczenia (chyba że kod pobiera wskaźnik do jej adresu), kompilator kontroluje wszystkie punkty wywołania do niej.
Oznacza to, że można swobodnie używać niestandardowego interfejsu ABI, całkowicie go wbudować lub wykonać dowolną liczbę innych optymalizacji, które mogą nie być możliwe w przypadku funkcji z połączeniem zewnętrznym.
@caf Co masz na myśli mówiąc, że adres funkcji jest zajęty? Dla mnie pojęcie funkcji / zmiennej posiadającej adresy lub przypisywanego adresowi w czasie kompilacji jest trochę zagmatwane. Czy możesz to rozwinąć?
SayeedHussain
2
@crypticcoder: Twój program jest ładowany do pamięci, dlatego funkcje mają również lokalizację pamięci i można uzyskać adres. Za pomocą wskaźnika funkcji możesz wywołać dowolne z nich. Jeśli to zrobisz, zmniejszy to listę optymalizacji, które może wykonać kompilator, ponieważ kod musi pozostać nienaruszony w tym samym miejscu.
5
@crypticcoder: mam na myśli to, że wyrażenie ocenia wskaźnik do funkcji i robi z nim coś innego niż natychmiastowe wywołanie funkcji. Jeśli wskaźnik do staticfunkcji wymyka się z bieżącej jednostki tłumaczeniowej, wówczas funkcja ta może być wywołana bezpośrednio z innych jednostek tłumaczeniowych.
kawiarnia
@caf, jeśli adres funkcji jest zajęty, czy kompilator to wykryje i wyłączy statyczne optymalizacje funkcji wspomniane w tej odpowiedzi (np. używając niestandardowego ABI)? Przypuszczam, że musiałoby.
sevko
28
Słowo statickluczowe w C jest używane w skompilowanym pliku (.c w przeciwieństwie do .h), więc funkcja istnieje tylko w tym pliku.
Normalnie, kiedy tworzysz funkcję, kompilator generuje cruft, którego konsolidator może użyć do, no cóż, połączenia wywołania funkcji z tą funkcją. Jeśli użyjesz słowa kluczowego static, inne funkcje w tym samym pliku mogą wywołać tę funkcję (ponieważ można to zrobić bez uciekania się do konsolidatora), podczas gdy konsolidator nie ma informacji umożliwiających innym plikom dostęp do funkcji.
Programiści C używają statycznego atrybutu do ukrywania deklaracji zmiennych i funkcji wewnątrz modułów, podobnie jak w przypadku używania publicznych i prywatnych deklaracji w Javie i C ++. Pliki źródłowe w C pełnią rolę modułów. Każda zmienna globalna lub funkcja zadeklarowana z atrybutem statycznym jest prywatna dla tego modułu. Podobnie każda zmienna globalna lub funkcja zadeklarowana bez atrybutu statycznego jest publiczna i może być dostępna dla każdego innego modułu. Dobrą praktyką programistyczną jest ochrona zmiennych i funkcji za pomocą atrybutu statycznego, gdy tylko jest to możliwe.
odpowiedź pmg jest bardzo przekonująca. Jeśli chcesz wiedzieć, jak działają deklaracje statyczne na poziomie obiektu, poniższe informacje mogą być dla Ciebie interesujące. Ponownie użyłem tego samego programu napisanego przez pmg i skompilowałem go do pliku .so (obiekt współdzielony)
Następująca zawartość jest po zrzuceniu pliku .so do czegoś czytelnego dla człowieka
0000000000000675 f1 : adres funkcji f1
000000000000068c f2 : adres funkcji f2 (staticc)
zwróć uwagę na różnicę w adresie funkcji, to coś znaczy. W przypadku funkcji zadeklarowanej z innym adresem może to bardzo dobrze oznaczać, że f2 znajduje się bardzo daleko lub w innym segmencie pliku obiektowego.
Linkery używają czegoś, co nazywa się PLT (tablica powiązań procedur) i GOT (tablica globalnych przesunięć), aby zrozumieć symbole, do których mają dostęp.
Na razie pomyśl, że GOT i PLT w magiczny sposób wiążą wszystkie adresy, a sekcja dynamiczna zawiera informacje o wszystkich tych funkcjach, które są widoczne dla linkera.
Po zrzuceniu dynamicznej sekcji pliku .so otrzymujemy kilka wpisów, ale interesują nas tylko funkcje f1 i f2 .
Sekcja dynamiczna zawiera tylko wpis dla funkcji f1 pod adresem 0000000000000675, a nie dla f2 !
Num: Wartość Rozmiar Typ Bind Vis Ndx Nazwa
9:000000000000067523 FUNC GLOBAL DEFAULT 11 f1
I to wszystko !. Z tego jasno wynika, że linkerowi nie powiedzie się znalezienie funkcji f2, ponieważ nie ma jej w sekcji dynamicznej pliku .so.
Gdy zajdzie potrzeba ograniczenia dostępu do niektórych funkcji, podczas definiowania i deklarowania funkcji użyjemy słowa kluczowego static.
/* file ab.c */staticvoid function1(void){
puts("function1 called");}And store the following code in another file ab1.c
/* file ab1.c */int main(void){
function1();
getchar();return0;}/* in this code, we'll get a "Undefined reference to function1".Because function 1 is declared static in file ab.c and can't be used in ab1.c */
Odpowiedzi:
Utworzenie funkcji
static
ukrywa ją przed innymi jednostkami tłumaczeniowymi, co pomaga zapewnić hermetyzację .helper_file.c
main.c :
źródło
#include <helper_file.c>
? Myślę, że wtedy byłaby to pojedyncza jednostka tłumaczeniowa ...gcc -std=c99 -pedantic -Wall -Wextra main.c helper_file.c
. Prototypy funkcji znajdują się w obu plikach źródłowych (nie ma potrzeby plików nagłówkowych). Konsolidator rozwiąże funkcje.pmg mówi o hermetyzacji; poza ukryciem funkcji przed innymi jednostkami tłumaczeniowymi (a raczej z tego powodu ), tworzenie funkcji
static
może również przynosić korzyści w zakresie wydajności w obecności optymalizacji kompilatora.Ponieważ
static
funkcji nie można wywołać z dowolnego miejsca spoza bieżącej jednostki tłumaczenia (chyba że kod pobiera wskaźnik do jej adresu), kompilator kontroluje wszystkie punkty wywołania do niej.Oznacza to, że można swobodnie używać niestandardowego interfejsu ABI, całkowicie go wbudować lub wykonać dowolną liczbę innych optymalizacji, które mogą nie być możliwe w przypadku funkcji z połączeniem zewnętrznym.
źródło
static
funkcji wymyka się z bieżącej jednostki tłumaczeniowej, wówczas funkcja ta może być wywołana bezpośrednio z innych jednostek tłumaczeniowych.Słowo
static
kluczowe w C jest używane w skompilowanym pliku (.c w przeciwieństwie do .h), więc funkcja istnieje tylko w tym pliku.Normalnie, kiedy tworzysz funkcję, kompilator generuje cruft, którego konsolidator może użyć do, no cóż, połączenia wywołania funkcji z tą funkcją. Jeśli użyjesz słowa kluczowego static, inne funkcje w tym samym pliku mogą wywołać tę funkcję (ponieważ można to zrobić bez uciekania się do konsolidatora), podczas gdy konsolidator nie ma informacji umożliwiających innym plikom dostęp do funkcji.
źródło
Patrząc na powyższe posty, chciałbym zwrócić uwagę na jeden szczegół.
Załóżmy, że nasz główny plik („main.c”) wygląda następująco:
Rozważmy teraz trzy przypadki:
Przypadek 1: Nasz plik nagłówkowy („header.h”) wygląda następująco:
Następnie następujące polecenie w systemie Linux:
odniesie sukces ! Po tym, jeśli ktoś biegnie
Dane wyjściowe będą
Wywołanie funkcji w nagłówku
Co powinna wydrukować ta statyczna funkcja.
Przypadek 2: Nasz plik nagłówkowy („header.h”) wygląda następująco:
i mamy jeszcze jeden plik „header.c”, który wygląda tak:
Następnie następujące polecenie
wyświetli błąd.
Przypadek 3:
Podobnie jak w przypadku 2, z tą różnicą, że teraz nasz plik nagłówkowy („header.h”) to:
Wtedy to samo polecenie co w przypadku 2 zakończy się sukcesem, a dalsze wykonanie ./main da oczekiwany rezultat.
Więc z tych testów (wykonanych na komputerze Acer x86, Ubuntu OS) założyłem, że
Słowo kluczowe static zapobiega wywołaniu funkcji w innym pliku * .c niż jest to zdefiniowane.
Popraw mnie, jeśli się mylę.
źródło
Programiści C używają statycznego atrybutu do ukrywania deklaracji zmiennych i funkcji wewnątrz modułów, podobnie jak w przypadku używania publicznych i prywatnych deklaracji w Javie i C ++. Pliki źródłowe w C pełnią rolę modułów. Każda zmienna globalna lub funkcja zadeklarowana z atrybutem statycznym jest prywatna dla tego modułu. Podobnie każda zmienna globalna lub funkcja zadeklarowana bez atrybutu statycznego jest publiczna i może być dostępna dla każdego innego modułu. Dobrą praktyką programistyczną jest ochrona zmiennych i funkcji za pomocą atrybutu statycznego, gdy tylko jest to możliwe.
źródło
odpowiedź pmg jest bardzo przekonująca. Jeśli chcesz wiedzieć, jak działają deklaracje statyczne na poziomie obiektu, poniższe informacje mogą być dla Ciebie interesujące. Ponownie użyłem tego samego programu napisanego przez pmg i skompilowałem go do pliku .so (obiekt współdzielony)
Następująca zawartość jest po zrzuceniu pliku .so do czegoś czytelnego dla człowieka
0000000000000675 f1 : adres funkcji f1
000000000000068c f2 : adres funkcji f2 (staticc)
zwróć uwagę na różnicę w adresie funkcji, to coś znaczy. W przypadku funkcji zadeklarowanej z innym adresem może to bardzo dobrze oznaczać, że f2 znajduje się bardzo daleko lub w innym segmencie pliku obiektowego.
Linkery używają czegoś, co nazywa się PLT (tablica powiązań procedur) i GOT (tablica globalnych przesunięć), aby zrozumieć symbole, do których mają dostęp.
Na razie pomyśl, że GOT i PLT w magiczny sposób wiążą wszystkie adresy, a sekcja dynamiczna zawiera informacje o wszystkich tych funkcjach, które są widoczne dla linkera.
Po zrzuceniu dynamicznej sekcji pliku .so otrzymujemy kilka wpisów, ale interesują nas tylko funkcje f1 i f2 .
Sekcja dynamiczna zawiera tylko wpis dla funkcji f1 pod adresem 0000000000000675, a nie dla f2 !
Num: Wartość Rozmiar Typ Bind Vis Ndx Nazwa
I to wszystko !. Z tego jasno wynika, że linkerowi nie powiedzie się znalezienie funkcji f2, ponieważ nie ma jej w sekcji dynamicznej pliku .so.
źródło
Gdy zajdzie potrzeba ograniczenia dostępu do niektórych funkcji, podczas definiowania i deklarowania funkcji użyjemy słowa kluczowego static.
źródło