funkcja statyczna w C


Odpowiedzi:


212

Utworzenie funkcji staticukrywa ją przed innymi jednostkami tłumaczeniowymi, co pomaga zapewnić hermetyzację .

helper_file.c

int f1(int);        /* prototype */
static int 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) {
    return 42 + 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 */
    return 0;
}

8
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.


9
... chyba że adres funkcji jest zajęty.
kawiarnia

1
@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.


1
3Doub: Użycie słowa „cruft” jest bardziej precyzyjne niż przypuszczasz. W kontekście pytania „cruft” jest tutaj właściwym słowem.
Erik Aronesty,

@ 3Doubloons Zgadzam się, że jest to uproszczone, ale myślę, że jest to o wiele łatwiejsze do zrozumienia dla początkujących.
Ingo Bürk

11

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:

#include "header.h"

int main(void) {
    FunctionInHeader();
}

Rozważmy teraz trzy przypadki:

  • Przypadek 1: Nasz plik nagłówkowy („header.h”) wygląda następująco:

    #include <stdio.h>
    
    static void FunctionInHeader();
    
    void FunctionInHeader() {
        printf("Calling function inside header\n");
    }

    Następnie następujące polecenie w systemie Linux:

    gcc main.c header.h -o main

    odniesie sukces ! Po tym, jeśli ktoś biegnie

    ./main

    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:

    static void FunctionInHeader();     

    i mamy jeszcze jeden plik „header.c”, który wygląda tak:

    #include <stdio.h>
    
    #include "header.h"
    
    void FunctionInHeader() {
        printf("Calling function inside header\n");
    }

    Następnie następujące polecenie

    gcc main.c header.h header.c -o main

    wyświetli błąd.

  • Przypadek 3:

    Podobnie jak w przypadku 2, z tą różnicą, że teraz nasz plik nagłówkowy („header.h”) to:

    void FunctionInHeader(); // keyword static removed

    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ę.


5

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.


4

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: 0000000000000675    23 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.


0

Gdy zajdzie potrzeba ograniczenia dostępu do niektórych funkcji, podczas definiowania i deklarowania funkcji użyjemy słowa kluczowego static.

            /* file ab.c */ 
static void function1(void) 
{ 
  puts("function1 called"); 
} 
And store the following code in another file ab1.c

/* file ab1.c  */ 
int main(void) 
{ 
 function1();  
  getchar(); 
  return 0;   
} 
/* 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 */

Ta odpowiedź nie jest zbyt pomocna.
fiscblog
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.