web-dev-qa-db-fra.com

Comment ce programme C se compile et s'exécute avec deux fonctions principales?

Aujourd'hui, en travaillant avec une bibliothèque personnalisée, j'ai trouvé un comportement étrange. Un code de bibliothèque statique contenait une fonction de débogage main(). Ce n'était pas à l'intérieur d'un drapeau #define. Il est donc également présent en bibliothèque. Et il est utilisé comme lien vers un autre programme qui contenait la vraie main().
Lorsque les deux sont liés ensemble, l'éditeur de liens n'a pas généré d'erreur de déclaration multiple pour main(). Je me demandais comment cela pouvait arriver.

Pour faire simple, j'ai créé un exemple de programme qui simule le même comportement:

$ cat prog.c
#include <stdio.h>
int main()
{
        printf("Main in prog.c\n");
}

$ cat static.c
#include <stdio.h>
int main()
{
        printf("Main in static.c\n");
}

$ gcc -c static.c
$ ar rcs libstatic.a static.o
$ gcc prog.c -L. -lstatic -o 2main
$ gcc -L. -lstatic -o 1main

$ ./2main
Main in prog.c
$ ./1main
Main in static.c

Comment le binaire "2main" trouve-t-il main à exécuter?

Mais la compilation des deux donne une erreur de déclaration multiple:

$ gcc prog.c static.o
static.o: In function `main':
static.c:(.text+0x0): multiple definition of `main'
/tmp/ccrFqgkh.o:prog.c:(.text+0x0): first defined here
collect2: ld returned 1 exit status

Quelqu'un peut-il expliquer ce comportement?

65
MrPavanayi

Citant ld (1):

L'éditeur de liens recherchera une archive une seule fois, à l'emplacement où il est spécifié sur la ligne de commande. Si l'archive définit un symbole qui n'était pas défini dans un objet apparu avant l'archive sur la ligne de commande, l'éditeur de liens inclura le ou les fichiers appropriés de l'archive.

Lors de la liaison de 2main, le symbole principal est résolu avant que ld n'atteigne -lstatic, car ld le récupère dans prog.o.

Lorsque vous liez 1main, vous avez un principal non défini au moment où il arrive à -lstatic, donc il recherche l'archive pour principal.

Cette logique ne s'applique qu'aux archives (bibliothèques statiques), pas aux objets normaux. Lorsque vous liez prog.o et static.o, tous les symboles des deux objets sont inclus sans condition, vous obtenez donc une erreur de définition en double.

57
arsv

Lorsque vous liez une bibliothèque statique (.a), l'éditeur de liens ne recherche dans l'archive que si des symboles non définis ont été suivis jusqu'à présent. Sinon, il ne regarde pas du tout l'archive. Alors votre 2main cas, il ne regarde jamais l'archive car il n'a pas de symboles indéfinis pour faire l'unité de traduction.

Si vous incluez une fonction simple dans static.c:

#include <stdio.h>
void fun()
{
      printf("This is fun\n");
}   
int main()
{
      printf("Main in static.c\n");
}

et appelez-le depuis prog.c, alors l'éditeur de liens sera obligé de regarder l'archive pour trouver le symbole fun et vous obtiendrez la même erreur de définition principale multiple que l'éditeur de liens trouverait maintenant le symbole en double main.

Lorsque vous compilez directement les fichiers objets (comme dans gcc a.o b.o), l'éditeur de liens n'a aucun rôle ici et tous les symboles sont inclus pour créer un seul symbole binaire et évidemment dupliqué.

L'essentiel est que l'éditeur de liens regarde l'archive uniquement s'il manque des symboles. Sinon, c'est aussi bien que de ne pas lier avec des bibliothèques.

17
P.P.

Une fois que l'éditeur de liens a chargé tous les fichiers d'objets, il recherche dans les bibliothèques symboles non définis. S'il n'y en a pas, aucune bibliothèque ne doit être lue. Puisque main a été défini, même s'il trouve un main dans chaque bibliothèque, il n'y a aucune raison d'en charger une seconde.

Les linkers ont cependant des comportements radicalement différents. Par exemple, si votre bibliothèque incluait un fichier objet contenant à la fois main () et foo () et que foo n'était pas défini, vous obtiendriez très probablement une erreur pour un symbole défini à plusieurs reprises main ().

Les éditeurs de liens modernes (tautologiques) omettront les symboles globaux des objets inaccessibles - par ex. AIX. Les éditeurs de liens de style ancien comme ceux trouvés sur Solaris et les systèmes Linux se comportent toujours comme les éditeurs de liens Unix des années 1970, chargeant tous les symboles d'un module objet, accessibles ou non. Cela peut être une source d'horribles ballonnements ainsi que des temps de liaison excessifs.

Les linkers * nix sont également caractérisés par le fait qu'ils ne recherchent efficacement une bibliothèque qu'une seule fois à chaque fois qu'elle est répertoriée. Cela oblige le programmeur à ordonner les bibliothèques sur la ligne de commande à un éditeur de liens ou dans un fichier make, en plus d'écrire un programme. Ne pas exiger une liste ordonnée des bibliothèques n'est pas moderne. Les anciens systèmes d'exploitation avaient souvent des éditeurs de liens qui rechercheraient toutes les bibliothèques à plusieurs reprises jusqu'à ce qu'une passe ne parvienne pas à résoudre un symbole.

1
Fred Mitchell