webentwicklung-frage-antwort-db.com.de

Statische Funktionen, die in "C" -Headerdateien deklariert sind

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:

  • Was ist das Problem der Deklaration statischer Funktionen in Header-Dateien?
  • Was sind die Risiken?
  • Welchen Einfluss hat die Kompilierungszeit?
  • Gibt es ein Risiko in der Laufzeit?
18
miguel azevedo

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.


  • Was ist das Problem, statische Funktionen in Header-Dateien zu deklarieren?
    Es ist etwas ungewöhnlich. In der Regel sind statische Funktionen Funktionen, die nur in einer Datei benötigt werden. Sie werden als statisch deklariert, um dies zu verdeutlichen, indem ihre Sichtbarkeit eingeschränkt wird. Sie in einem Header zu deklarieren, ist daher ein wenig gegensätzlich. Wenn die Funktion in der Tat in mehreren Dateien mit identischen Definitionen verwendet wird, sollte sie extern mit einer einzigen Definition erstellt werden. Wenn nur eine Übersetzungseinheit es tatsächlich verwendet, gehört die Deklaration nicht in einen Header.

    Ein mögliches Szenario besteht daher darin, eine einheitliche Funktionssignatur für verschiedene Implementierungen in den jeweiligen Übersetzungseinheiten sicherzustellen. Der gemeinsame Header führt zu einem Kompilierungsfehler für verschiedene Rückgabetypen in C (und C++); verschiedene Parametertypen würde nur in C einen Kompilierfehler verursachen (aber nicht in C++ 'wegen Funktionsüberladung).
  • Was sind die Risiken?
    Ich sehe keine Risiken in Ihrem Szenario. (Im Gegensatz dazu muss die Funktion definition in einem Header enthalten sein, der möglicherweise gegen das Kapselungsprinzip verstößt.)
  • Wie wirkt sich das auf die Kompilierungszeit aus?
    Eine Funktionsdeklaration ist klein und ihre Komplexität gering, so dass der Aufwand für zusätzliche Funktionsdeklarationen in einem Header wahrscheinlich vernachlässigbar ist. Wenn Sie jedoch einen zusätzlichen Header erstellen und einfügen für die Deklaration in vielen Übersetzungseinheiten, kann der Overhead für die Dateibehandlung erheblich sein (d. H. Der Compiler befindet sich viel im Leerlauf, während er auf die Header-E/A wartet).
  • Gibt es ein Risiko in der Laufzeit?
    Ich kann keine sehen.
13

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:


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


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

10
Nominal Animal

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.

0
JHBonarius