«Inline» sans «static» ou «extern» est-il jamais utile dans C99?

96

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 inlineou extern inlineau lieu de just inline, ou si je compile avec -O(la fonction est donc 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 inlinespécificateur de fonction sans extern, alors 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 bien tout cela, une unité de compilation avec une fonction définie inlinecomme dans l'exemple ci-dessus ne compile de manière cohérente que s'il existe également une fonction externe avec le même nom, et je ne sais jamais si ma propre fonction ou la fonction externe est appelée.

Ce comportement n'est-il pas complètement idiot? Est-il jamais utile de définir une fonction inlinesans staticou externen C99? Est-ce que je manque quelque chose?

Résumé des réponses

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

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

inline void f() {}

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

extern inline void f();

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

Comme l'explique la logique du comité C99 citée dans la réponse de Jonathan , il inlines'agit d'optimisations du compilateur qui nécessitent la définition d'une fonction pour être visible sur le site d'un appel. Ceci ne peut être réalisé qu'en mettant 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'elle est vue par le compilateur. Mais puisque le compilateur n'est pas obligé de réellement insérer une fonction, une définition externe doit exister quelque part.

Sven Marnach
la source
borderline duplicate of stackoverflow.com/questions/5369888/…
Earlz
@Earlz: Merci pour le lien. Ma question porte sur la justification du paragraphe cité de la norme, et s'il y a jamais des cas d'utilisation pour inlinesans staticet extern, cependant. Malheureusement, aucune de ces questions n'est abordée dans cette question.
Sven Marnach
@Nemo: J'ai lu cette question et les réponses avant de publier la mienne. Encore une fois, je ne demande pas "comment se comporte-t-il?" mais plutôt "quelle est l'idée derrière ce comportement"? Je suis presque sûr qu'il me manque quelque chose ici.
Sven Marnach
1
oui, c'est pourquoi j'ai dit «limite».
Personnellement,

Réponses:

40

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

Que fait extern inline?

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, pour élaborer]

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

Nemo
la source
1
Merci, je commence à avoir l'idée!
Sven Marnach
8
Ouais, je n'ai jamais compris cela moi-même jusqu'à maintenant, alors merci d'avoir demandé :-). C'est bizarre en ce que la définition "inline" non-extern va dans l'en-tête (mais n'entraîne pas nécessairement de génération de code du tout), alors que la déclaration "extern inline" va dans le fichier .c et provoque en fait le code à être généré.
Nemo
3
Cela signifie-t-il que j'écris inline void f() {}dans l'en-tête et extern inline void f();dans le fichier .c? Donc, la définition de la fonction réelle va dans l'en-tête et le fichier .c contient une simple déclaration dans ce cas, en inversant l'ordre habituel?
Sven Marnach
1
@endolith: Je ne comprends pas votre question. Nous discutons inline sans static ou extern. Bien sûr, static inlinec'est bien, mais ce n'est pas le sujet de cette question et réponse.
Nemo
2
@MatthieuMoy Vous devez utiliser à la -std=c99place de -std=gnu89.
a3f
27

D'après la norme (ISO / CEI 9899: 1999) elle-même:

Annexe J.2 Comportement non défini

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

Le comité C99 a rédigé une justification , et il dit:

6.7.4 Spécificateurs de fonction

Une nouvelle fonctionnalité de C99: Le inlinemot-clé, adapté de C ++, est un spécificateur de fonction qui ne peut être utilisé que dans les déclarations de fonction. Il est utile pour les optimisations de programme qui nécessitent la définition d'une fonction pour être visible sur le site d'un appel. (Notez que la norme ne tente 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 inlinemot - clé 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 devraient être optimisés de préférence aux appels d'autres fonctions déclarées sans le inlinemot - clé.

La visibilité est un problème pour un appel d'une fonction avec lien externe où l'appel est dans une unité de traduction différente de la définition de la fonction. Dans ce cas, le inlinemot - clé 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 funcavec un lien externe se produit là où une définition en ligne est visible, le comportement est le même que si l'appel était fait à une autre fonction, par exemple __func, avec un lien 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 utilisant la définition en ligne, ni sur l'implémentation 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 à saddret utiliser la définition externe pour l'autre, il n'est pas garanti que l'opération d'égalité s'évalue à 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 non- constobjet de ce type.

L'inlining a été ajouté à la norme de manière à pouvoir être implémenté avec la technologie de l'éditeur de liens existante, et un sous-ensemble d'inlining C99 est compatible avec C ++. Ceci a été réalisé 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. Comme cette spécification consiste simplement en une déclaration qui ne inlinecontient pas le mot clé ou qui contient les deux inlineet extern, elle sera également acceptée par un traducteur C ++.

L'intégration en C99 étend la spécification C ++ de deux manières. Premièrement, si une fonction est déclarée inlinedans une unité de traduction, elle n'a pas besoin d'être déclarée inlinedans toutes les autres unités de traduction. Cela permet, par exemple, une fonction de bibliothèque qui doit être incorporé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 ne fait 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 doit pas dépendre du fait qu'un appel est implémenté avec une définition en ligne visible, ou la définition externe, d'un fonction. Cela permet à une définition en ligne d'être spécialisée pour son utilisation dans 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 préoccupés par la compatibilité peuvent simplement se conformer aux règles plus strictes du C ++.

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 certains codes hérités qui redéclarent les fonctions de bibliothèque standard après avoir inclus leurs en-têtes. Le inlinemot-clé est uniquement destiné à fournir aux utilisateurs un moyen portable de suggérer l'intégration de fonctions. Étant donné que les en-têtes standard n'ont pas besoin d'être portables, les implémentations ont d'autres options telles que:

#define abs(x) __builtin_abs(x)

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

Jonathan Leffler
la source
Merci, c'est très détaillé - et à peu près aussi lisible que la norme elle-même. :-) Je savais que je devais manquer quelque chose. Pourriez-vous donner une référence d'où vous avez obtenu cela?
Sven Marnach
Je viens de penser que vous pouvez utiliser Google pour trouver des liens: open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf
Sven Marnach
@Sven: j'ai emprunté l'URL de votre recherche et l'ai mise dans la réponse. Le document que j'ai utilisé était une copie de la justification que j'avais précédemment enregistrée (en 2005), mais c'était aussi V5.10.
Jonathan Leffler
4
Merci encore. L'idée la plus précieuse que j'ai tirée de la réponse est qu'il existe un document contenant la justification de la norme C99 (et il existe probablement également des documents pour d'autres normes). Alors que la réponse de Nemo m'a donné un poisson, celui-ci m'a appris à pêcher.
Sven Marnach
0

> 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 if plutôt que d' 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.

Ainsi, si vous connaissez le comportement de la fonction fet que vous souhaitez l'appeler en boucle serrée, 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). Le rédacteur du compilateur, cependant, a la possibilité d'optimiser la taille du programme à la place.

Fred Foo
la source
2
La norme stipule que le compilateur peut toujours supposer qu'il existe une fonction externe avec le même nom et l'appeler à la place de la version en ligne, donc je ne pense pas que ce soit un bogue du compilateur. J'ai essayé avec gcc 4.3, 4.4 et 4.5, tous donnent une erreur de l'éditeur de liens.
Sven Marnach