Was bringt es, eine Funktion in C statisch zu machen?
Was bringt es, eine Funktion in C statisch zu machen?
Antworten:
Wenn Sie eine Funktion static
erstellen, 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;
}
#include <helper_file.c>
? Ich denke, das würde es dann zu einer einzigen Übersetzungseinheit machen ...
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 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 static
auch bei Compileroptimierungen Leistungsvorteile bringen.
Da eine static
Funktion 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.
static
Funktion der aktuellen Übersetzungseinheit entgeht, kann diese Funktion direkt von anderen Übersetzungseinheiten aufgerufen werden.
Das static
Schlü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.
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.
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.
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.
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 */