statische Funktion in C.


172

Was bringt es, eine Funktion in C statisch zu machen?


7
@nightcracker: In C ++ gibt es keine "Methoden". Ich denke, Sie sind mit Objective-C verwechselt.
Bo Persson

1
Nein, ich bin verwirrt mit Python. Eine Funktion innerhalb einer Klasse wird in Python als Methode bezeichnet.
Orlp

Antworten:


212

Wenn Sie eine Funktion staticerstellen, wird sie vor anderen Übersetzungseinheiten ausgeblendet, wodurch die Kapselung erleichtert wird .

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
Ist die Übersetzungseinheit die richtige Terminologie? Wäre die Objektdatei nicht genauer? Soweit ich weiß, ist dem Linker eine statische Funktion verborgen, und der Linker arbeitet nicht mit Übersetzungseinheiten.
Steven Eckhoff

2
Ich hätte auch sagen sollen, dass ich es gerne als vor dem Linker verborgen betrachte; es scheint so klarer.
Steven Eckhoff

1
Also, interne Funktion (die wir sicher nicht außerhalb der c-Datei aufrufen dürfen), wir sollten sie als statische Funktion setzen, oder? Wir können also sicher sein, dass es nicht anderswo anrufen kann. Danke :)
hqt

1
Wie kompilierst du das? Haben Sie verwenden #include <helper_file.c>? Ich denke, das würde es dann zu einer einzigen Übersetzungseinheit machen ...
Atcold

2
@Atcold: So wie ich den Code geschrieben habe, fügen Sie einfach die 2 Quelldateien wie folgt in die Befehlszeile ein gcc -std=c99 -pedantic -Wall -Wextra main.c helper_file.c. Die Prototypen für die Funktionen sind in beiden Quelldateien vorhanden (keine Header-Dateien erforderlich). Der Linker löst die Funktionen auf.
PMG

80

pmg ist genau richtig in Bezug auf die Einkapselung; Neben dem Ausblenden der Funktion vor anderen Übersetzungseinheiten (oder besser gesagt aufgrund dessen) kann das Erstellen von Funktionen staticauch bei Compileroptimierungen Leistungsvorteile bringen.

Da eine staticFunktion von keiner Stelle außerhalb der aktuellen Übersetzungseinheit aufgerufen werden kann (es sei denn, der Code nimmt einen Zeiger auf seine Adresse), steuert der Compiler alle Aufrufpunkte darin.

Dies bedeutet, dass es kostenlos ist, ein nicht standardmäßiges ABI zu verwenden, es vollständig zu integrieren oder eine beliebige Anzahl anderer Optimierungen durchzuführen, die für eine Funktion mit externer Verknüpfung möglicherweise nicht möglich sind.


9
... es sei denn, die Adresse der Funktion wird übernommen.
Café

1
@caf Was meinst du mit der Adresse der Funktion? Für mich ist die Vorstellung, dass Funktionen / Variablen Adressen haben oder zur Kompilierungszeit eine Adresse zugewiesen bekommen, etwas verwirrend. Können Sie bitte näher darauf eingehen?
SayeedHussain

2
@crypticcoder: Ihr Programm wird in den Speicher geladen, daher haben Funktionen auch einen Speicherort und die Adresse kann abgerufen werden. Mit einem Funktionszeiger können Sie jeden dieser Aufrufe aufrufen. Wenn Sie dies tun, wird die Liste der Optimierungen reduziert, die der Compiler ausführen kann, da der Code an derselben Stelle intakt bleiben muss.

5
@crypticcoder: Ich meine, ein Ausdruck wertet einen Zeiger auf die Funktion aus und macht etwas anderes damit, als die Funktion sofort aufzurufen. Wenn ein Zeiger auf eine staticFunktion der aktuellen Übersetzungseinheit entgeht, kann diese Funktion direkt von anderen Übersetzungseinheiten aufgerufen werden.
Café

@caf Wenn die Adresse der Funktion verwendet wird, würde der Compiler dies erkennen und die in dieser Antwort erwähnten statischen Funktionsoptimierungen deaktivieren (z. B. unter Verwendung eines nicht standardmäßigen ABI)? Ich nehme an, es müsste sein.
Sevko

28

Das staticSchlüsselwort in C wird in einer kompilierten Datei verwendet (.c im Gegensatz zu .h), sodass die Funktion nur in dieser Datei vorhanden ist.

Normalerweise generiert der Compiler beim Erstellen einer Funktion eine Cruft, mit der der Linker einen Funktionsaufruf mit dieser Funktion verknüpfen kann. Wenn Sie das Schlüsselwort static verwenden, können andere Funktionen in derselben Datei diese Funktion aufrufen (da dies ohne Rückgriff auf den Linker möglich ist), während der Linker keine Informationen hat, mit denen andere Dateien auf die Funktion zugreifen können.


1
3Doub: Die Verwendung des Wortes "Cruft" ist präziser, als Sie es glauben. Im Kontext der Frage ist "Cruft" hier das richtige Wort.
Erik Aronesty

@ 3Doubloons Ich stimme zu, dass es vereinfacht ist, aber ich denke, das macht es für Anfänger viel einfacher zu verstehen.
Ingo Bürk

11

In den obigen Beiträgen möchte ich auf ein Detail hinweisen.

Angenommen, unsere Hauptdatei ("main.c") sieht folgendermaßen aus:

#include "header.h"

int main(void) {
    FunctionInHeader();
}

Betrachten Sie nun drei Fälle:

  • Fall 1: Unsere Header-Datei ("header.h") sieht folgendermaßen aus:

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

    Dann der folgende Befehl unter Linux:

    gcc main.c header.h -o main

    wird gelingen ! Danach, wenn man rennt

    ./main

    Die Ausgabe wird sein

    Aufruffunktion im Header

    Welches ist, was diese statische Funktion drucken sollte.

  • Fall 2: Unsere Header-Datei ("header.h") sieht folgendermaßen aus:

    static void FunctionInHeader();     

    und wir haben auch noch eine Datei "header.c", die so aussieht:

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

    Dann den folgenden Befehl

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

    wird einen Fehler geben.

  • Fall 3:

    Ähnlich wie in Fall 2, außer dass jetzt unsere Header-Datei ("header.h") lautet:

    void FunctionInHeader(); // keyword static removed

    Dann ist der gleiche Befehl wie in Fall 2 erfolgreich, und eine weitere Ausführung von ./main ergibt das erwartete Ergebnis.

Aus diesen Tests (ausgeführt auf einem Acer x86-Computer, Ubuntu OS) ging ich davon aus, dass

Das statische Schlüsselwort verhindert, dass Funktionen in einer anderen * .c-Datei als der definierten aufgerufen werden.

Korrigieren Sie mich, wenn ich falsch liege.


5

C-Programmierer verwenden das statische Attribut, um Variablen- und Funktionsdeklarationen in Modulen auszublenden, ähnlich wie Sie öffentliche und private Deklarationen in Java und C ++ verwenden würden. C-Quelldateien spielen die Rolle von Modulen. Jede globale Variable oder Funktion, die mit dem statischen Attribut deklariert wurde, ist für dieses Modul privat. Ebenso ist jede globale Variable oder Funktion, die ohne das statische Attribut deklariert wurde, öffentlich und kann von jedem anderen Modul aufgerufen werden. Es ist eine gute Programmierpraxis, Ihre Variablen und Funktionen nach Möglichkeit mit dem statischen Attribut zu schützen.


4

Die Antwort von pmg ist sehr überzeugend. Wenn Sie wissen möchten, wie statische Deklarationen auf Objektebene funktionieren, könnten diese Informationen für Sie interessant sein. Ich habe dasselbe von pmg geschriebene Programm wiederverwendet und es in eine .so-Datei (Shared Object) kompiliert

Der folgende Inhalt befindet sich nach dem Ablegen der .so-Datei in einer für Menschen lesbaren Datei

0000000000000675 f1 : Adresse der Funktion f1

000000000000068c f2 : Adresse der Funktion f2 (staticc)

Beachten Sie den Unterschied in der Funktionsadresse, es bedeutet etwas. Für eine Funktion, die mit einer anderen Adresse deklariert ist, kann dies sehr gut bedeuten, dass f2 sehr weit entfernt oder in einem anderen Segment der Objektdatei lebt.

Linker verwenden PLT (Procedure Linkage Table) und GOT (Global Offsets Table), um Symbole zu verstehen, auf die sie zugreifen können.

Denken Sie vorerst, dass GOT und PLT alle Adressen auf magische Weise binden und ein dynamischer Abschnitt Informationen zu all diesen Funktionen enthält, die für den Linker sichtbar sind.

Nach dem Speichern des dynamischen Abschnitts der .so-Datei erhalten wir eine Reihe von Einträgen, die jedoch nur an den Funktionen f1 und f2 interessiert sind .

Der dynamische Abschnitt enthält nur den Eintrag für die Funktion f1 unter der Adresse 0000000000000675 und nicht für die Funktion f2 !

Num: Wert Größe Typ Bind Vis Ndx Name

 9: 0000000000000675    23 FUNC    GLOBAL DEFAULT   11 f1

Und das ist es !. Daraus wird deutlich, dass der Linker die f2- Funktion nicht finden kann, da sie sich nicht im dynamischen Abschnitt der .so-Datei befindet.


0

Wenn der Zugriff auf einige Funktionen eingeschränkt werden muss, verwenden wir das statische Schlüsselwort, um eine Funktion zu definieren und zu deklarieren.

            /* 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 */

Diese Antwort ist nicht sehr hilfreich.
Fiscblog
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.