Que font en réalité les linkers avec les fonctions `inline` définies par plusieurs?

En C et C ++, inline fonctions en inline avec une liaison externe peuvent bien entendu avoir plusieurs définitions disponibles au moment du lien, l’hypothèse étant que ces définitions sont toutes (espérons-le) identiques. (Je me réfère bien sûr aux fonctions déclarées avec la spécification de liaison inline , et non aux fonctions que le compilateur ou l’optimiseur de lien-temps insère réellement.)

Alors, que font généralement les lieurs lorsqu’ils rencontrent plusieurs définitions d’une fonction? En particulier:

  • Toutes les définitions sont-elles incluses dans l’exécutable final ou dans la bibliothèque partagée?
  • Tous les invocations de la fonction sont-elles liées à la même définition?
  • Les réponses aux questions ci-dessus sont-elles requirejses par une ou plusieurs des normes ISO C et C ++ et, dans la négative, les plates-formes les plus courantes font-elles la même chose?

PS Oui, je sais que C et C ++ sont des langages distincts, mais ils prennent tous les deux en charge inline , et la sortie de leur compilateur peut généralement être liée par le même éditeur de liens (par exemple, le ld de GCC). .

L’éditeur de liens doit juste comprendre comment dédupliquer toutes les définitions. Ceci est bien sûr à condition que toute définition de fonction ait été émise; les fonctions en ligne peuvent bien être en ligne. Mais si vous prenez l’adresse d’une fonction en ligne avec une liaison externe, vous obtenez toujours la même adresse (cf. [dcl.fct.spec] / 4).

Les fonctions en ligne ne sont pas la seule construction qui nécessite le support de l’éditeur de liens; les templates en sont une autre, de même que les variables inline (en C ++ 17).

En fait, si la fonction est en ligne, il n’ya rien à relier. Ce n’est que lorsque, pour une raison quelconque, le compilateur décide de ne pas développer la fonction en ligne qu’il doit générer une version hors ligne de la fonction. Si le compilateur génère une version hors ligne de la fonction pour plusieurs unités de traduction, vous obtenez plusieurs fichiers objects contenant des définitions pour la même fonction “inline”.

La définition out-of-line est compilée dans le fichier object et est marquée de sorte que l’éditeur de liens ne se plaint pas s’il existe plusieurs définitions de ce nom. S’il y en a plus d’un, l’éditeur de liens en choisit simplement un. Habituellement, le premier qu’il a vu, mais ce n’est pas obligatoire, et si les définitions sont toutes identiques, cela n’a pas d’importance. Et c’est la raison pour laquelle le comportement indéfini consiste à avoir deux définitions différentes ou plus de la même fonction en ligne: il n’ya pas de règle à choisir. Tout peut arriver.

inline ou no inline , C n’autorise pas plusieurs définitions externes du même nom parmi les unités de traduction consortingbuant au même programme ou à la même bibliothèque. De plus, il ne permet pas plusieurs définitions du même nom dans la même unité de traduction, qu’elles soient internes, externes ou en ligne. Par conséquent, il peut y avoir au maximum deux définitions disponibles d’une fonction donnée dans une unité de traduction donnée: une interne et / ou en ligne et une externe.

C 2011, 6.7.4 / 7 a ceci à dire:

Toute fonction avec une liaison interne peut être une fonction en ligne. Pour une fonction avec une liaison externe, les ressortingctions suivantes s’appliquent: Si une fonction est déclarée avec un spécificateur de fonction inline , elle doit également être définie dans la même unité de traduction. Si toutes les déclarations d’étendue de fichier pour une fonction dans une unité de traduction incluent le spécificateur de fonction en 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.

(Soulignement ajouté.)

En réponse spécifique à vos questions, en ce qui concerne C:

Toutes les définitions sont-elles incluses dans l’exécutable final ou dans la bibliothèque partagée?

Les définitions en ligne ne sont pas des définitions externes. Elles peuvent ou non être incluses en tant que fonctions réelles, en tant que code intégré, à la fois ou non, en fonction des faiblesses du compilateur et de l’éditeur de liens et des détails de leur utilisation. Elles ne peuvent en aucun cas être appelées nommément par des fonctions appartenant à différentes unités de traduction. Par conséquent, déterminer si elles doivent être considérées comme “incluses” est une question plutôt abstraite.

Tous les invocations de la fonction sont-elles liées à la même définition?

C ne spécifie pas, mais il permet à la réponse d’être “non”, même pour différents appels dans la même unité de traduction. De plus, les fonctions en ligne n’étant pas externes, aucune fonction en ligne définie dans une unité de traduction n’est appelée (directement) par une fonction définie dans une unité de traduction différente.

Les réponses aux questions ci-dessus sont-elles requirejses par une ou plusieurs des normes ISO C et C ++ et, dans la négative, les plates-formes les plus courantes font-elles la même chose?

Mes réponses sont basées sur le standard C actuel dans la mesure où il répond aux questions, mais comme vous l’avez vu, ces réponses ne sont pas tout à fait normatives. De plus, la norme ne traite directement aucune question de code d’object ou de liaison. Vous avez donc peut-être remarqué que mes réponses ne sont pas, pour la plupart, formulées en ces termes.

En tout état de cause, il n’est pas prudent de supposer qu’un système C donné est cohérent même avec lui-même à cet égard pour différentes fonctions ou différents contextes. Dans certaines circonstances, chaque appel à une fonction interne ou inline peut être intégré, de sorte que cette fonction n’apparaisse pas du tout comme une fonction distincte. D’autres fois, il peut en effet émettre une fonction avec une liaison interne, mais cela ne l’empêche pas d’inclure de toute façon certains appels à cette fonction. Dans tous les cas, les fonctions internes ne sont pas éligibles pour être liées aux fonctions d’autres unités de traduction, de sorte que l’éditeur de liens n’est pas nécessairement impliqué dans leur liaison.

Je pense que la réponse correcte à votre question est “ça dépend”.

Considérez les morceaux de code suivants:

Fichier xc (ou x.cc):

 #include  void otherfunction(void); inline void inlinefunction(void) { printf("inline 1\n"); } int main(void) { inlinefunction(); otherfunction(); return 0; } 

Fichier yc (ou y.cc)

 #include  inline void inlinefunction(void) { printf("inline 2\n"); } void otherfunction(void) { printf("otherfunction\n"); inlinefunction(); } 

Comme inline mot-clé inline n’est qu’une “suggestion” pour que la compilation insère en ligne la fonction, différents compilateurs avec des drapeaux différents se comportent différemment. Par exemple, le compilateur C ressemble toujours aux “exportations” de fonctions en ligne et ne permet pas plusieurs définitions:

 $ gcc xc yc && ./a.out /tmp/ccy5GYHp.o: In function `inlinefunction': yc:(.text+0x0): multiple definition of `inlinefunction' /tmp/ccQkn7m4.o:xc:(.text+0x0): first defined here collect2: ld returned 1 exit status 

tandis que C ++ le permet:

 $ g++ x.cc y.cc && ./a.out inline 1 otherfunction inline 1 

Plus intéressant – essayons de changer l’ordre des fichiers (et donc – changez l’ordre des liens):

 $ g++ y.cc x.cc && ./a.out inline 2 otherfunction inline 2 

Eh bien … on dirait que le premier compte! Mais … ajoutons des indicateurs d’optimisation:

 $ g++ y.cc x.cc -O1 && ./a.out inline 1 otherfunction inline 2 

Et c’est le comportement auquel nous nous attendions. La fonction est en ligne. Un ordre différent des fichiers ne change rien:

 $ g++ x.cc y.cc -O1 && ./a.out inline 1 otherfunction inline 2 

Ensuite, nous pouvons étendre notre source xc (x.cc) avec le prototype de void anotherfunction(void) et l’appeler dans notre fonction main . Plaçons une anotherfunction définition de fonction dans le fichier zc (z.cc):

 #include  void inlinefunction(void); void anotherfunction(void) { printf("anotherfunction\n"); inlinefunction(); } 

Nous ne définissons pas le corps de inlinefunction cette fois. La compilation / exécution pour c ++ donne les résultats suivants:

 $ g++ x.cc y.cc z.cc && ./a.out inline 1 otherfunction inline 1 anotherfunction inline 1 

Ordre différent:

 $ g++ y.cc x.cc z.cc && ./a.out inline 2 otherfunction inline 2 anotherfunction inline 2 

Optimisation:

 $ g++ x.cc y.cc z.cc -O1 && ./a.out /tmp/ccbDnQqX.o: In function `anotherfunction()': z.cc:(.text+0xf): undefined reference to `inlinefunction()' collect2: ld returned 1 exit status 

La conclusion est donc la suivante: le mieux est de déclarer inline avec static , ce qui restreint la scope de l’utilisation de la fonction, car “exporter” la fonction que nous aimerions utiliser en ligne n’a aucun sens.