Für mich ist es eine Regel, statische Funktionen in Quelldateien zu definieren und zu deklarieren, ich meine .c-Dateien.
In sehr seltenen Situationen habe ich jedoch Leute gesehen, die dies in der Header-Datei deklariert haben. Da statische Funktionen über interne Verknüpfungen verfügen, müssen Sie diese in jeder Datei definieren, in der die Header-Datei enthalten ist, in der die Funktion deklariert ist. Das sieht ziemlich seltsam aus und ist weit von dem entfernt, was wir normalerweise wollen, wenn wir etwas als statisch deklarieren.
Auf der anderen Seite wird sich der Compiler beschweren, wenn jemand, der naiv ist, versucht, diese Funktion zu nutzen, ohne sie zu definieren. In gewisser Hinsicht ist es also nicht wirklich unsicher, dies auch nur seltsam anzuhören.
Meine Fragen sind:
Zunächst möchte ich mein Verständnis der von Ihnen beschriebenen Situation verdeutlichen: Der Header enthält (nur) eine statische Funktionsdeklaration, während die C-Datei die Definition enthält, d. H. Den Quellcode der Funktion. Beispielsweise
some.h:
static void f();
// potentially more declarations
some.c:
#include "some.h"
static void f() { printf("Hello world\n"); }
// more code, some of it potentially using f()
Wenn dies die von Ihnen beschriebene Situation ist, werde ich Ihre Bemerkung in Frage stellen
Da statische Funktionen über eine interne Verknüpfung verfügen, müssen Sie diese in jeder Datei definieren, in der die Header-Datei deklariert ist.
Wenn Sie die Funktion deklarieren, aber in einer bestimmten Übersetzungseinheit nicht verwenden, müssen Sie sie meines Erachtens nicht definieren. gcc akzeptiert das mit einer Warnung; Der Standard scheint es nicht zu verbieten, es sei denn, ich habe etwas verpasst. Dies kann in Ihrem Szenario wichtig sein, da Übersetzungseinheiten, die die Funktion nicht verwenden, aber den Header mit ihrer Deklaration enthalten, keine nicht verwendete Definition angeben müssen.
Dies ist keine Antwort auf die gestellten Fragen, zeigt aber hoffentlich, dass warum eine Funktion static
(oder static inline
) in einer Header-Datei implementiert werden kann.
Ich persönlich kann mir nur zwei gute Gründe vorstellen, um einige Funktionen static
in einer Header-Datei zu deklarieren:
Wenn die Header-Datei eine Schnittstelle vollständig implementiert, die nur in der aktuellen Kompilierungseinheit sichtbar sein soll
Dies ist äußerst selten, kann jedoch z.B. ein pädagogischer Kontext, irgendwann während der Entwicklung einer Beispielbibliothek; oder vielleicht bei der Anbindung an eine andere Programmiersprache mit minimalem Code.
Ein Entwickler kann sich dafür entscheiden, wenn die Bibliotheks- oder Schnittstellenimplementierung trivial und nahezu trivial ist und die Benutzerfreundlichkeit (für den Entwickler unter Verwendung der Header-Datei) wichtiger ist als die Codegröße. In diesen Fällen werden in den Deklarationen in der Header-Datei häufig Präprozessor-Makros verwendet, sodass dieselbe Header-Datei mehrmals eingeschlossen werden kann, was in C eine Art groben Polymorphismus zur Folge hat.
Hier ein praktisches Beispiel: Shoot-yourself-in-the-Foot-Spielplatz für lineare kongruente Pseudozufallszahlengeneratoren. Da die Implementierung lokal für die Kompilierungseinheit ist, erhält jede Kompilierungseinheit ihre eigenen Kopien des PRNG. Dieses Beispiel zeigt auch, wie roher Polymorphismus in C implementiert werden kann.
prng32.h :
#if defined(PRNG_NAME) && defined(PRNG_MULTIPLIER) && defined(PRNG_CONSTANT) && defined(PRNG_MODULUS)
#define MERGE3_(a,b,c) a ## b ## c
#define MERGE3(a,b,c) MERGE3_(a,b,c)
#define NAME(name) MERGE3(PRNG_NAME, _, name)
static uint32_t NAME(state) = 0U;
static uint32_t NAME(next)(void)
{
NAME(state) = ((uint64_t)PRNG_MULTIPLIER * (uint64_t)NAME(state) + (uint64_t)PRNG_CONSTANT) % (uint64_t)PRNG_MODULUS;
return NAME(state);
}
#undef NAME
#undef MERGE3
#endif
#undef PRNG_NAME
#undef PRNG_MULTIPLIER
#undef PRNG_CONSTANT
#undef PRNG_MODULUS
Ein Beispiel unter Verwendung des obigen Beispiels -prng32.h :
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#define PRNG_NAME glibc
#define PRNG_MULTIPLIER 1103515245UL
#define PRNG_CONSTANT 12345UL
#define PRNG_MODULUS 2147483647UL
#include "prng32.h"
/* provides glibc_state and glibc_next() */
#define PRNG_NAME borland
#define PRNG_MULTIPLIER 22695477UL
#define PRNG_CONSTANT 1UL
#define PRNG_MODULUS 2147483647UL
#include "prng32.h"
/* provides borland_state and borland_next() */
int main(void)
{
int i;
glibc_state = 1U;
printf("glibc lcg: Seed %u\n", (unsigned int)glibc_state);
for (i = 0; i < 10; i++)
printf("%u, ", (unsigned int)glibc_next());
printf("%u\n", (unsigned int)glibc_next());
borland_state = 1U;
printf("Borland lcg: Seed %u\n", (unsigned int)borland_state);
for (i = 0; i < 10; i++)
printf("%u, ", (unsigned int)borland_next());
printf("%u\n", (unsigned int)borland_next());
return EXIT_SUCCESS;
}
Der Grund für das Markieren sowohl der _state
-Variablen als auch der _next()
-Funktion static
ist, dass auf diese Weise jede Kompilierungseinheit, die die Header-Datei enthält, eine eigene Kopie der Variablen und der Funktionen hat. ihre eigene Kopie des PRNG. Jeder muss natürlich separat ausgesät werden; und wenn auf den gleichen Wert gesetzt, ergibt sich die gleiche Reihenfolge.
Man sollte solche Polymorphismus-Versuche in C generell scheuen, da dies zu komplizierten Präprozessor-Makro-Spielereien führt, wodurch die Implementierung viel schwieriger zu verstehen, zu warten und zu modifizieren ist als nötig.
Wenn jedoch exploring der Parameterraum einiger Algorithmen ist - wie hier die Typen 2-Bit-Linearkongruenzgeneratoren , können wir für jeden eine einzige Implementierung verwenden der von uns untersuchten Generatoren, um sicherzustellen, dass zwischen ihnen keine Implementierungsunterschiede bestehen. Beachten Sie, dass auch dieser Fall eher einem Entwicklungstool ähnelt und nicht in einer Implementierung enthalten ist, die von anderen verwendet werden kann.
Wenn der Header einfache static inline
Accessorfunktionen implementiert
Präprozessor-Makros werden häufig verwendet, um den Code-Zugriff auf komplizierte Strukturtypen zu vereinfachen. Die Funktionen von static inline
sind ähnlich, bieten jedoch auch eine Typprüfung beim Kompilieren und können mehrmals auf ihre Parameter verweisen (bei Makros ist dies problematisch).
Ein praktischer Anwendungsfall ist eine einfache Schnittstelle zum Lesen von Dateien mit POSIX.1 I/O auf niedriger Ebene (unter Verwendung von <unistd.h>
und <fcntl.h>
anstelle von <stdio.h>
). Ich habe das selbst gemacht, wenn ich sehr große Textdateien (Dutzende von Megabyte bis Gigabyte) gelesen habe, die reelle Zahlen (mit einem benutzerdefinierten Gleitkomma-/Doppelparser) als GNU C-Standard-E/A enthalten ist nicht besonders schnell.
Zum Beispiel inbuffer.h :
#ifndef INBUFFER_H
#define INBUFFER_H
typedef struct {
unsigned char *head; /* Next buffered byte */
unsigned char *tail; /* Next byte to be buffered */
unsigned char *ends; /* data + size */
unsigned char *data;
size_t size;
int descriptor;
unsigned int status; /* Bit mask */
} inbuffer;
#define INBUFFER_INIT { NULL, NULL, NULL, NULL, 0, -1, 0 }
int inbuffer_open(inbuffer *, const char *);
int inbuffer_close(inbuffer *);
int inbuffer_skip_slow(inbuffer *, const size_t);
int inbuffer_getc_slow(inbuffer *);
static inline int inbuffer_skip(inbuffer *ib, const size_t n)
{
if (ib->head + n <= ib->tail) {
ib->head += n;
return 0;
} else
return inbuffer_skip_slow(ib, n);
}
static inline int inbuffer_getc(inbuffer *ib)
{
if (ib->head < ib->tail)
return *(ib->head++);
else
return inbuffer_getc_slow(ib);
}
#endif /* INBUFFER_H */
Beachten Sie, dass die obigen inbuffer_skip()
und inbuffer_getc()
nicht prüfen, ob ib
nicht NULL ist. Dies ist typisch für solche Funktionen. Es wird angenommen, dass diese Accessorfunktionen "auf dem schnellen Weg" sind, d. H. Sehr oft aufgerufen werden. In solchen Fällen ist sogar der Funktionsaufruf-Overhead von Bedeutung (und wird mit static inline
-Funktionen vermieden, da sie im Code an der Aufrufstelle dupliziert werden).
Triviale Accessor-Funktionen, wie die oben genannten inbuffer_skip()
und inbuffer_getc()
, können den Compiler auch die mit Funktionsaufrufen verbundenen Registerbewegungen vermeiden lassen, da Funktionen erwarten, dass sich ihre Parameter in bestimmten Registern oder auf dem Stack befinden, während inline-Funktionen angepasst werden können (wrt. register use) auf den Code, der die eingebettete Funktion umgibt.
Persönlich empfehle ich, zuerst einige Testprogramme mit den nicht inlinierten Funktionen zu schreiben und die Leistung und die Ergebnisse mit den inlinierten Versionen zu vergleichen. Wenn Sie die Ergebnisse vergleichen, stellen Sie sicher, dass die inline-Versionen keine Bugs aufweisen (hier ist es nur eine Art von Bugs!), Und wenn Sie die Leistung und die generierten Binärdateien (mindestens die Größe) vergleichen, sehen Sie, ob sich Inlining im Allgemeinen lohnt.
Warum möchten Sie eine globale und eine statische Funktion? In c sind Funktionen standardmäßig global. Sie verwenden statische Funktionen nur, wenn Sie den Zugriff auf eine Funktion auf die Datei beschränken möchten, für die sie deklariert sind. Sie schränken den Zugriff also aktiv ein, indem Sie ihn für statisch erklären ...
Die einzige Voraussetzung für Implementierungen in der Header-Datei sind C++ - Template-Funktionen und Template-Klassen-Member-Funktionen.