Pour moi, c'est une règle pour définir et déclarer des fonctions statiques dans les fichiers source, je veux dire les fichiers .c.
Cependant, dans de très rares situations, j'ai vu des gens le déclarer dans le fichier d'en-tête. Étant donné que les fonctions statiques ont un lien interne, nous devons le définir dans chaque fichier, nous incluons le fichier d'en-tête où la fonction est déclarée. Cela semble assez étrange et loin de ce que nous voulons habituellement lorsque nous déclarons quelque chose de statique.
D'un autre côté, si quelqu'un de naïf essaie d'utiliser cette fonction sans la définir, le compilateur se plaindra. Donc, dans un certain sens, il n'est pas vraiment dangereux de faire cela, même si cela semble étrange.
Mes questions sont:
J'aimerais d'abord clarifier ma compréhension de la situation que vous décrivez: l'en-tête contient (uniquement) une déclaration de fonction statique tandis que le fichier C contient la définition, c'est-à-dire le code source de la fonction. Par exemple
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()
Si telle est la situation que vous décrivez, je conteste votre remarque
Étant donné que les fonctions statiques ont un lien interne, nous devons le définir dans chaque fichier, nous incluons le fichier d'en-tête où la fonction est déclarée.
Si vous déclarez la fonction mais ne l'utilisez pas dans une unité de traduction donnée, je ne pense pas que vous ayez à la définir. gcc accepte cela avec un avertissement; la norme ne semble pas l'interdire, sauf si j'ai raté quelque chose. Cela peut être important dans votre scénario car les unités de traduction qui n'utilisent pas la fonction mais incluent l'en-tête avec sa déclaration n'ont pas à fournir de définition inutilisée.
Ce n'est pas une réponse aux questions posées, mais nous espérons que pourquoi on pourrait implémenter une fonction static
(ou static inline
) Dans un fichier d'en-tête.
Personnellement, je ne peux penser qu'à deux bonnes raisons de déclarer certaines fonctions static
dans un fichier d'en-tête:
Si le fichier d'en-tête implémente complètement une interface qui ne devrait être visible que dans l'unité de compilation actuelle
Ceci est extrêmement rare, mais pourrait être utile par exemple un contexte éducatif, à un moment donné au cours du développement d'un exemple de bibliothèque; ou peut-être lors de l'interfaçage avec un autre langage de programmation avec un minimum de code.
Un développeur peut choisir de le faire si la bibliothèque ou l'implémentation inter-interface est triviale et presque ainsi, et la facilité d'utilisation (pour le développeur utilisant le fichier d'en-tête) est plus importante que la taille du code. Dans ces cas, les déclarations dans le fichier d'en-tête utilisent souvent des macros de préprocesseur, permettant au même fichier d'en-tête d'être inclus plusieurs fois, fournissant une sorte de polymorphisme brut en C.
Voici un exemple pratique: terrain de jeu de tir sur pied pour les générateurs de nombres pseudo-aléatoires linéaires congruents. Étant donné que l'implémentation est locale à l'unité de compilation, chaque unité de compilation recevra ses propres copies du PRNG. Cet exemple montre également comment le polymorphisme brut peut être implémenté en C.
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
Un exemple utilisant ce qui précède, example-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;
}
La raison du marquage à la fois de la variable _state
Et de la fonction _next()
static
est que chaque unité de compilation qui inclut le fichier d'en-tête a sa propre copie des variables et la fonctions - ici, leur propre copie du PRNG. Chacun doit être semé séparément, bien sûr; et s'il est ensemencé à la même valeur, donnera la même séquence.
Il faut généralement éviter ces tentatives de polymorphisme en C, car cela conduit à des manigances de macro de préprocesseur compliquées, ce qui rend l'implémentation beaucoup plus difficile à comprendre, à maintenir et à modifier que nécessaire.
Cependant, lorsque explore l'espace des paramètres de certains algorithmes - comme ici, les types de générateurs congruents linéaires 32 bits , cela nous permet d'utiliser une seule implémentation pour chaque des générateurs que nous examinons, en veillant à ce qu'il n'y ait pas de différences de mise en œuvre entre eux. Notez que même ce cas ressemble plus à un outil de développement, et non quelque chose que vous devriez voir dans une implémentation fournie aux autres.
Si l'en-tête implémente de simples fonctions d'accesseur static inline
Les macros de préprocesseur sont couramment utilisées pour simplifier le code d'accès aux types de structure complexes. Les fonctions static inline
Sont similaires, sauf qu'elles fournissent également une vérification de type au moment de la compilation et peuvent faire référence à leurs paramètres plusieurs fois (avec les macros, ce qui est problématique).
Un cas d'utilisation pratique est une interface simple pour lire des fichiers à l'aide d'E/S POSIX.1 de bas niveau (en utilisant <unistd.h>
Et <fcntl.h>
Au lieu de <stdio.h>
). Je l'ai fait moi-même lors de la lecture de très gros fichiers texte (des dizaines de mégaoctets à gigaoctets) contenant des nombres réels (avec un flotteur/double analyseur personnalisé), comme le GNU C E/S standard n'est pas particulièrement rapide.
Par exemple, 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 */
Notez que les inbuffer_skip()
et inbuffer_getc()
ci-dessus ne vérifient pas si ib
n'est pas NULL; cela est typique de ces fonctions. Ces fonctions d'accesseur sont supposées être "dans la voie rapide", c'est-à-dire appelées très souvent. Dans de tels cas, même la surcharge d'appel de la fonction est importante (et est évitée avec les fonctions static inline
, Car elles sont dupliquées dans le code sur le site de l'appel).
Les fonctions accessoires triviales, comme les inbuffer_skip()
et inbuffer_getc()
ci-dessus, peuvent également permettre au compilateur d'éviter les mouvements de registre impliqués dans les appels de fonction, car les fonctions s'attendent à ce que leurs paramètres soient situés dans des registres spécifiques ou sur la pile, tandis que les fonctions en ligne peuvent être adaptées (par rapport à l'utilisation du registre) au code entourant la fonction en ligne.
Personnellement, je recommande d'abord d'écrire quelques programmes de test en utilisant les fonctions non intégrées et de comparer les performances et les résultats aux versions intégrées. La comparaison des résultats garantit que les versions intégrées n'ont pas de bogues (désactivée par un type est courant ici!), Et la comparaison des performances et des binaires générés (taille, au moins) vous indique si l'inlining en vaut la peine en général.
Pourquoi voudriez-vous une fonction à la fois globale et statique? En c, les fonctions sont globales par défaut. Vous n'utilisez des fonctions statiques que si vous souhaitez limiter l'accès à une fonction au fichier dans lequel elles sont déclarées. Vous restreignez donc activement l'accès en le déclarant statique ...
La seule exigence pour les implémentations dans le fichier d'en-tête concerne les fonctions de modèle c ++ et les fonctions membres de classe de modèle.