web-dev-qa-db-fra.com

«Inline» sans «statique» ou «extern» est-il jamais utile en C99?

Quand j'essaye de construire ce code

inline void f() {}

int main()
{
    f();
}

en utilisant la ligne de commande

gcc -std=c99 -o a a.c

J'obtiens une erreur de l'éditeur de liens (référence non définie à f). L'erreur disparaît si j'utilise static inline ou extern inline au lieu de simplement inline, ou si je compile avec -O (donc la fonction est en fait en ligne).

Ce comportement semble être défini au paragraphe 6.7.4 (6) de la norme C99:

Si toutes les déclarations de portée de fichier pour une fonction dans une unité de traduction incluent le spécificateur de fonction inline sans extern, la définition dans cette unité de traduction est une définition en ligne. Une définition en ligne ne fournit pas de définition externe pour la fonction et n'interdit pas une définition externe dans une autre unité de traduction. Une définition en ligne fournit une alternative à une définition externe, qu'un traducteur peut utiliser pour implémenter tout appel à la fonction dans la même unité de traduction. Il n'est pas spécifié si un appel à la fonction utilise la définition en ligne ou la définition externe.

Si je comprends tout cela correctement, une unité de compilation avec une fonction définie inline comme dans l'exemple ci-dessus ne compile de manière cohérente que s'il existe également une fonction externe du même nom, et je ne sais jamais si ma propre fonction ou la la fonction externe est appelée.

Ce comportement n'est-il pas complètement idiot? Est-il jamais utile de définir une fonction inline sans static ou extern en C99? Suis-je en train de manquer quelque chose?

Résumé des réponses

Bien sûr, il me manquait quelque chose, et le comportement n'est pas idiot. :)

Comme Nemo explique , l'idée est de mettre la définition de la fonction

inline void f() {}

dans le fichier d'en-tête et uniquement une déclaration

extern inline void f();

dans le fichier .c correspondant. Seule la déclaration extern déclenche la génération de code binaire visible de l'extérieur. Et il n'y a en effet aucune utilisation de inline dans un fichier .c - il n'est utile que dans les en-têtes.

Comme l'explique justification du comité C99 citée dans la réponse de Jonathan , inline concerne les optimisations du compilateur qui nécessitent que la définition d'une fonction soit visible sur le site d'un appel. Cela ne peut être réalisé qu'en plaçant la définition dans l'en-tête, et bien sûr une définition dans un en-tête ne doit pas émettre de code chaque fois qu'il est vu par le compilateur. Mais comme le compilateur n'est pas obligé de réellement incorporer une fonction, une définition externe doit exister quelque part.

90
Sven Marnach

En fait, cette excellente réponse répond également à votre question, je pense:

en ligne externe

L'idée est que "inline" peut être utilisé dans un fichier d'en-tête, puis "extern inline" dans un fichier .c. "extern inline" est juste la façon dont vous indiquez au compilateur quel fichier objet doit contenir le code généré (visible de l'extérieur).

[mettre à jour, élaborer]

Je ne pense pas qu'il soit utile d'utiliser "inline" (sans "statique" ou "extern") dans un fichier .c. Mais dans un fichier d'en-tête, cela a du sens, et cela nécessite une déclaration "extern inline" correspondante dans un fichier .c pour générer réellement le code autonome.

37
Nemo

De la norme (ISO/IEC 9899: 1999) elle-même:

Annexe J.2 Comportement indéfini

  • ...
  • Une fonction avec liaison externe est déclarée avec un spécificateur de fonction inline, mais n'est pas également définie dans la même unité de traduction (6.7.4).
  • ...

Le Comité C99 a écrit un Justification , et il dit:

6.7.4 Spécificateurs de fonction

Une nouvelle fonctionnalité de C99: Le mot clé inline, adapté de C++, est une fonction - spécificateur qui ne peut être utilisé que dans les déclarations de fonction. Il est utile pour les optimisations de programme qui nécessitent que la définition d'une fonction soit visible sur le site d'un appel. (Notez que la norme n'essaie pas de spécifier la nature de ces optimisations.)

La visibilité est assurée si la fonction a une liaison interne ou si elle a une liaison externe et que l'appel est dans la même unité de traduction que la définition externe. Dans ces cas, la présence du mot clé inline dans une déclaration ou une définition de la fonction n'a aucun effet au-delà de l'indication d'une préférence selon laquelle les appels de cette fonction doivent être optimisés de préférence aux appels d'autres fonctions déclarées sans inline mot clé.

La visibilité est un problème pour un appel d'une fonction avec une liaison externe où l'appel est dans une unité de traduction différente de la définition de la fonction. Dans ce cas, le mot clé inline permet à l'unité de traduction contenant l'appel de contenir également une définition locale ou en ligne de la fonction.

Un programme peut contenir une unité de traduction avec une définition externe, une unité de traduction avec une définition en ligne et une unité de traduction avec une déclaration mais pas de définition pour une fonction. Les appels dans cette dernière unité de traduction utiliseront la définition externe comme d'habitude.

Une définition en ligne d'une fonction est considérée comme une définition différente de la définition externe. Si un appel à une fonction func avec liaison externe se produit lorsqu'une définition en ligne est visible, le comportement est le même que si l'appel était effectué vers une autre fonction, par exemple __func, avec liaison interne. Un programme conforme ne doit pas dépendre de la fonction appelée. Il s'agit du modèle en ligne de la norme.

Un programme conforme ne doit pas s'appuyer sur l'implémentation en utilisant la définition en ligne, ni s'appuyer sur l'implémentation en utilisant la définition externe. L'adresse d'une fonction est toujours l'adresse correspondant à la définition externe, mais lorsque cette adresse est utilisée pour appeler la fonction, la définition en ligne peut être utilisée. Par conséquent, l'exemple suivant peut ne pas se comporter comme prévu.

inline const char *saddr(void)
{
    static const char name[] = "saddr";
    return name;
}
int compare_name(void)
{
    return saddr() == saddr(); // unspecified behavior
}

Étant donné que l'implémentation peut utiliser la définition en ligne pour l'un des appels à saddr et utiliser la définition externe pour l'autre, l'opération d'égalité n'est pas garantie d'être évaluée à 1 (vrai). Cela montre que les objets statiques définis dans la définition en ligne sont distincts de leur objet correspondant dans la définition externe. Cela a motivé la contrainte de ne même pas définir un objet nonconst de ce type.

La mise en ligne a été ajoutée à la norme de manière à pouvoir être mise en œuvre avec la technologie de l'éditeur de liens existante, et un sous-ensemble de mise en ligne C99 est compatible avec C++. Cet objectif a été atteint en exigeant qu'une seule unité de traduction contenant la définition d'une fonction en ligne soit spécifiée comme celle qui fournit la définition externe de la fonction. Étant donné que cette spécification consiste simplement en une déclaration qui n'a pas le mot clé inline ou contient à la fois inline et extern, elle sera également acceptée par un traducteur C++.

L'insertion en C99 étend la spécification C++ de deux manières. Premièrement, si une fonction est déclarée inline dans une unité de traduction, elle n'a pas besoin d'être déclarée inline dans toutes les autres unités de traduction. Cela permet, par exemple, une fonction de bibliothèque qui doit être insérée dans la bibliothèque mais disponible uniquement via une définition externe ailleurs. L'alternative d'utiliser une fonction wrapper pour la fonction externe nécessite un nom supplémentaire; et cela peut également avoir un impact négatif sur les performances si un traducteur n'effectue pas réellement de substitution en ligne.

Deuxièmement, l'exigence que toutes les définitions d'une fonction en ligne soient "exactement les mêmes" est remplacée par l'exigence selon laquelle le comportement du programme ne devrait pas dépendre de la mise en œuvre d'un appel avec une définition en ligne visible ou la définition externe d'un une fonction. Cela permet à une définition en ligne d'être spécialisée pour son utilisation au sein d'une unité de traduction particulière. Par exemple, la définition externe d'une fonction de bibliothèque peut inclure une validation d'argument qui n'est pas nécessaire pour les appels effectués à partir d'autres fonctions de la même bibliothèque. Ces extensions offrent certains avantages; et les programmeurs qui sont préoccupés par la compatibilité peuvent simplement se conformer aux règles C++ plus strictes.

Notez qu'il n'est pas approprié pour les implémentations de fournir des définitions en ligne des fonctions de bibliothèque standard dans les en-têtes standard, car cela peut casser du code hérité qui redéclare les fonctions de bibliothèque standard après avoir inclus leurs en-têtes. Le mot clé inline est uniquement destiné à fournir aux utilisateurs un moyen portable de suggérer l'inclusion de fonctions. Parce que les en-têtes standard n'ont pas besoin d'être portables, les implémentations ont d'autres options dans le sens de:

#define abs(x) __builtin_abs(x)

ou d'autres mécanismes non portables pour intégrer des fonctions de bibliothèque standard.

24
Jonathan Leffler

> J'obtiens une erreur de l'éditeur de liens (référence non définie à f)

Fonctionne ici: Linux x86-64, GCC 4.1.2. Peut être un bogue dans votre compilateur; Je ne vois rien dans le paragraphe cité de la norme qui interdit le programme donné. Notez l'utilisation de si plutôt que iff .

Une définition en ligne fournit une alternative à une définition externe , qu'un traducteur peut à utiliser pour implémenter tout appel à la fonction dans la même unité de traduction.

Donc, si vous connaissez le comportement de la fonction f et que vous voulez l'appeler dans une boucle étroite, vous pouvez copier-coller sa définition dans un module pour empêcher les appels de fonction; ou, vous pouvez fournir une définition qui, pour les besoins du module actuel, est équivalente (mais ignore la validation d'entrée ou toute optimisation que vous pouvez imaginer). Cependant, le rédacteur du compilateur a la possibilité d'optimiser la taille du programme.

0
Fred Foo