¿Cómo se compila y ejecuta este programa en C con dos funciones principales?

6 minutos de lectura

avatar de usuario
pavanayi

Hoy, mientras trabajaba con una biblioteca personalizada, encontré un comportamiento extraño. Un código de biblioteca estática contenía una depuración main() función. no estaba dentro de un #define bandera. Así que está presente en la biblioteca también. Y se usa como enlace a otro programa que contenía el verdadero main().
Cuando ambos están vinculados, el vinculador no arrojó un error de declaración múltiple para main(). Me preguntaba cómo podía pasar esto.

Para hacerlo simple, he creado un programa de muestra que simuló el mismo comportamiento:

$ 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

¿Cómo encuentra el binario “2main” qué main ¿ejecutar?

Pero compilar ambos juntos da un error de declaración múltiple:

$ 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

¿Alguien puede explicar este comportamiento?

  • Si tu corres nm en la biblioteca estática que contiene la primera main¿muestra que el símbolo main está definido (presente y no con un U junto a él)?

    – anol

    10 de abril de 2015 a las 8:01

  • el enlace dinámico buscaría solo los símbolos necesarios y dejaría el principal en libstatic.a

    – tristán

    10 de abril de 2015 a las 8:04

  • Otros tienen las respuestas, pero me gustaría comentar que el código que está compilando infringe la “Regla de una definición” (ODR), lo que significa un comportamiento indefinido. Simplemente está viendo cómo se ve ese comportamiento indefinido para un compilador.

    – Cort Amón

    11 de abril de 2015 a las 1:55

  • Relacionado: stackoverflow.com/questions/1449987/…

    – Pablo R.

    15 de abril de 2015 a las 5:18

Citando ld(1):

El enlazador buscará un archivo solo una vez, en la ubicación donde se especifica en la línea de comando. Si el archivo define un símbolo que no estaba definido en algún objeto que apareció antes del archivo en la línea de comando, el enlazador incluirá los archivos apropiados del archivo.

Al vincular 2main, el símbolo principal se resuelve antes de que ld llegue a -lstatic, porque ld lo toma de prog.o.

Al vincular 1main, tiene main indefinido en el momento en que llega a -lstatic, por lo que busca main en el archivo.

Esta lógica solo se aplica a los archivos (bibliotecas estáticas), no a los objetos normales. Cuando vincula prog.o y static.o, todos los símbolos de ambos objetos se incluyen incondicionalmente, por lo que obtiene un error de definición duplicada.

  • Tenga en cuenta que este comportamiento es específico del compilador (o, bueno, específico del enlazador): un enlazador de Microsoft buscaría todos los archivos y bibliotecas de objetos en su línea de comando y vomitaría un error si un símbolo está duplicado.

    – Medinoc

    10 de abril de 2015 a las 14:08

avatar de usuario
PÁGINAS

Cuando vincula una biblioteca estática (.a), el vinculador solo busca en el archivo si hubo algún símbolo indefinido rastreado hasta el momento. De lo contrario, no mira el archivo en absoluto. Entonces tus 2main caso, nunca mira el archivo ya que no tiene ningún símbolo indefinido para hacer la unidad de traducción.

Si incluye una función simple en static.c:

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

y llámalo desde prog.centonces el enlazador se verá obligado a mirar el archivo para encontrar el símbolo fun y obtendrá el mismo error de definición principal múltiple ya que el enlazador encontraría el símbolo duplicado main ahora.

Cuando compila directamente los archivos de objeto (como en gcc a.o b.o), el enlazador no tiene ningún papel aquí y todos los símbolos están incluidos para hacer un solo binario y obviamente hay símbolos duplicados.

La conclusión es que el enlazador mira el archivo solo si faltan símbolos. De lo contrario, es tan bueno como no vincularse con ninguna biblioteca.

  • Modifiqué static.c como dijiste y lo llamé desde prog.c. Ahora el enlazador mostró felizmente un error de definición múltiple. Así que me preguntaba si esto también es cierto para las bibliotecas compartidas. Pero parece que no. $ gcc -c -fPIC static.c $ gcc -shared -o libsharedlib.so static.o $ gcc -L. -lsharedlib prog.c -o 2mainso $ ./2mainso This is fun Main in prog.c ¿Puedes explicar esto también?

    – Sr. Pavanayi

    10 de abril de 2015 a las 9:59

  • No soy un experto en bibliotecas compartidas, pero afaik, la búsqueda de símbolos de biblioteca compartida es similar a la estática, excepto que el enlazador identifica y almacena el “punto de entrada” para el símbolo requerido que está en la biblioteca compartida. Entonces parece que cuando tienes .entonces eso contiene (fun y un duplicado main), el enlazador solo almacenaría “punto de entrada” para fun. Esa es mi interpretación de todos modos basada en lo que nm espectáculos (sólo U esta ahí fun que no sean llamadas libc).

    – PP

    10 de abril de 2015 a las 12:33

Después de que el enlazador carga cualquier archivo de objeto, busca en las bibliotecas simbolos indefinidos. Si no hay ninguno, entonces no es necesario leer bibliotecas. Dado que se ha definido main, incluso si encuentra un main en cada biblioteca, no hay razón para cargar una segunda.

Sin embargo, los enlazadores tienen comportamientos dramáticamente diferentes. Por ejemplo, si su biblioteca incluye un archivo de objeto con main () y foo () en él, y foo no estaba definido, es muy probable que obtenga un error para un símbolo principal definido de forma múltiple ().

Los enlazadores modernos (tautológicos) omitirán los símbolos globales de los objetos que son inalcanzables, por ejemplo, AIX. Los enlazadores de estilo antiguo como los que se encuentran en Solaris y los sistemas Linux todavía se comportan como los enlazadores de Unix de la década de 1970, cargando todos los símbolos de un módulo de objeto, accesible o no. Esto puede ser una fuente de hinchazón horrible, así como tiempos de enlace excesivos.

Otra característica de los enlazadores *nix es que buscan en una biblioteca solo una vez por cada vez que aparece en la lista. Esto exige que el programador ordene las bibliotecas en la línea de comando a un enlazador o en un archivo make, además de escribir un programa. No requerir una lista ordenada de bibliotecas no es moderno. Los sistemas operativos más antiguos a menudo tenían enlazadores que buscaban en todas las bibliotecas repetidamente hasta que un pase no lograba resolver un símbolo.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad