
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.

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 C11
capí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.

Ziffusión
Esto es lo que está pasando.
- Sin declaración de
f()
el compilador asume una declaración implícita como int f(void)
. Y luego felizmente compila a.c
.
- Al compilar
b.c
el 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.
- 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.
- 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 double
que se interpretará como int
a la vuelta en a.c
.

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.
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.