Declaraciones y enlaces de funciones implícitas

8 minutos de lectura

avatar de usuario
Edgar Rokjan

Recientemente he aprendido acerca de las declaraciones de funciones implícitas en C. La idea principal es clara, pero tengo algunos problemas para comprender el proceso de vinculación en este caso.

Considere el siguiente código (file C.A):

#include <stdio.h>

int main() {
    double someValue = f();
    printf("%f\n", someValue);
    return 0;
}

Si trato de compilarlo:

gcc -c a.c -std=c99

Veo una advertencia sobre la declaración implícita de función f().

Si trato de compilar y vincular:

gcc a.c -std=c99

Tengo un error de referencia indefinido. Así que todo está bien.

Luego agrego otro archivo (archivo antes de Cristo):

double f(double x) {
    return x;
}

E invoca el siguiente comando:

gcc a.c b.c -std=c99

Sorprendentemente, todo está vinculado con éxito. por supuesto despues ./a.fuera invocación Veo una salida basura.

Entonces, mi pregunta es: ¿Cómo se vinculan los programas con funciones declaradas implícitamente? ¿Y qué sucede en mi ejemplo bajo el capó del compilador/enlazador?

Leí varios temas sobre SO como este, este y este, pero todavía tengo problemas.

avatar de usuario
Sourav Ghosh

En primer lugar, desde C99 , la declaración implícita de una función se elimina del estándar. compiladores puede admita esto para la compilación de código heredado, pero no es nada obligatorio. Citando el prólogo estándar,

  • eliminar la declaración de función implícita

Dicho esto, según C11capítulo §6.5.2.2

Si la función se define con un tipo que no incluye un prototipo, y los tipos de los argumentos después de la promoción no son compatibles con los de los parámetros después de la promoción, el comportamiento no está definido.

Entonces, en tu caso,

  • la llamada de función en sí es una declaración implícita (que se convirtió en no estándar desde C99),

  • y debido a la falta de coincidencia de la firma de la función [Implicit declaration of a function were assumed to have an int return type]su código invoca comportamiento indefinido.

Solo para agregar un poco más de referencia, si intenta definir la función en el mismo unidad de compilación después la llamada, obtendrá un error de compilación debido a la firma no coincidente.

Sin embargo, su función se define en una unidad de compilación separada (y falta la declaración del prototipo), el compilador no tiene forma de verificar las firmas. Después de la compilación, el enlazador toma los archivos de objeto y, debido a la ausencia de verificación de tipos en el enlazador (y tampoco hay información en los archivos de objeto), felizmente los vincula. Finalmente, terminará en un exitoso compilación y enlace y UB.

  • Gracias por la respuesta detallada.

    – Edgar Rokjan

    04/01/2016 a las 20:23


avatar de usuario
Ziffusión

Esto es lo que está pasando.

  1. Sin declaración de f()el compilador asume una declaración implícita como int f(void). Y luego felizmente compila a.c.
  2. Al compilar b.cel compilador no tiene ninguna declaración previa para f()por lo que lo intuye de la definición de f(). Normalmente pondría alguna declaración de f() en un archivo de encabezado e incluirlo en ambos a.c y b.c. Debido a que ambos archivos verán la misma declaración, el compilador puede imponer la conformidad. Se quejará de la entidad que no coincida con la declaración. Pero en este caso, no hay un prototipo común al que referirse.
  3. En C, el compilador no almacena ninguna información sobre el prototipo en los archivos de objeto y el enlazador no realiza ninguna verificación de conformidad (no puede). Todo lo que ve es un símbolo sin resolver f en a.c y un símbolo f definido en b.c. Resuelve felizmente los símbolos y completa el enlace.
  4. Sin embargo, las cosas fallan en tiempo de ejecución, porque el compilador configura la llamada en a.c basado en el prototipo que asumió allí. Lo cual no coincide con lo que la definición en b.c busca. f() (desde b.c) obtendrá un argumento basura de la pila y lo devolverá como doubleque se interpretará como int a la vuelta en a.c.

  • Creo que el compilador asume int f() en vez de int f(void) cuando se trata de declaraciones implícitas.

    – Edgar Rokjan

    04/01/2016 a las 20:06

  • Lo comprobaré, pero dado que la llamada en a.c no tiene argumentos, tendría sentido suponer int f(void) porque int f() significa any number of arguments.

    – Ziffusión

    04/01/2016 a las 20:18

  • La declaración implícita no es válida en C moderno y es UB. No se proporciona uno implícito como int f(void);. Solo proporciona una declaración como int f(); El compilador no “intuye” al compilar b.cy no necesita tener un prototipo definido antes. Una definición de función también proporciona su prototipo. Si se proporcionó un prototipo antes, se compara con él. De otra forma no. El “prototipo común” no es una cosa. Un prototipo está disponible o no. Es irrelevante cómo ese compilador obtiene esa información (a través de un archivo de encabezado o definición real, etc.).

    – PP

    04/01/2016 a las 21:09

  • Si bien (3) y (4) son buenos, simplemente se define como comportamiento indefinido en el estándar.

    – PP

    04/01/2016 a las 21:09

  • @ I3x Todo esto es periférico a la pregunta, y en su mayoría es quisquilloso en la forma en que expresé algunas cosas. Pero lo que sea.

    – Ziffusión

    04/01/2016 a las 21:40

avatar de usuario
PÁGINAS

¿Cómo se vinculan los programas con funciones implícitamente declaradas? ¿Y qué sucede en mi ejemplo bajo el capó del compilador/enlazador?

Él int implícito La regla ha sido prohibida por el estándar C desde C99. entonces no es válido tener programas con declaraciones de funciones implícitas.

No es válido desde C99. Antes de eso, si un prototipo visible no está disponible, el compilador declara implícitamente uno con int tipo de retorno.

Sorprendentemente, todo está vinculado con éxito. Por supuesto, después de la invocación de ./a.out, veo una salida basura.

Debido a que no tenía un prototipo, el compilador declara implícitamente uno con int escribir para f(). Pero la definición real de f() devuelve un double. Los dos tipos son incompatibles y esto es comportamiento indefinido.

Esto no está definido incluso en C89/C90 en el que la regla int implícita es válida porque el prototipo implícito no es compatible con el tipo real. f() devoluciones. Así que este ejemplo es (con a.c y b.c) es indefinido en todos los estándares C.

Ya no es útil ni válido tener declaraciones de funciones implícitas. Entonces, el detalle real de cómo se maneja el compilador/enlazador es solo de interés histórico. Se remonta a los tiempos anteriores al estándar de K&R C que no tenían prototipos de funciones y las funciones regresan int por defecto. Se agregaron prototipos de funciones a C en el estándar C89/C90. En pocas palabras, usted deber tener prototipos (o definir funciones antes de usar) para todas las funciones en programas C válidos.

  • Esto no está completo. El problema no tiene nada que ver con la declaración implícita, sino que se debe al hecho de que el enlazador no sabe nada sobre tipos, encontró una definición de una función llamada f y eso es adecuado para un enlace. Para evitar este problema, algunos lenguajes utilizan la manipulación de nombres para escribir el código en el nombre de la función; pero este no es el caso en C. Tener prototipos no es suficiente, ¡necesita prototipos consistentes!

    – Jean-Baptiste Yunès

    04/01/2016 a las 20:31

  • @Jean-BaptisteYunès He explicado que los tipos de prototipo implícito y el real son incompatibles y, como resultado, se invoca a UB. No existe el “prototipo consistente”. Un prototipo es una información completa sobre el tipo de retorno de la función y sus parámetros.

    – PP

    04/01/2016 a las 20:38

  • @i3x pero la gran parte de la pregunta (creo) era por qué se completa el enlace y los detalles del proceso de enlace que lo explican.

    – Ziffusión

    04/01/2016 a las 20:39


  • @Jean-BaptisteYunès sí, el principal malentendido fue sobre el problema de vinculación en este caso. Leí respuestas de Sourav y l3x en consecuencia. Entonces se mezclaron un poco en mi cabeza. Ahora la respuesta de Sourav parece ser más completa.

    – Edgar Rokjan

    04/01/2016 a las 20:54

  • @Ziffusion Estoy diciendo que es UB y se compiló y vinculó debido a la antigua regla de C. Por cierto, acabo de leer su respuesta y no es del todo precisa.

    – PP

    04/01/2016 a las 20:58

Después de compilar, toda la información de tipo se pierde (excepto tal vez en la información de depuración, pero el enlazador no le presta atención). Lo único que queda es “hay un símbolo llamado “f” en la dirección 0xdeadbeef”.

El objetivo de los encabezados es informar a C sobre el tipo de símbolo, incluidos, para las funciones, qué argumentos toma y qué devuelve. Si no coincide con los reales con los que declara (ya sea explícita o implícitamente), obtiene un comportamiento indefinido.

¿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