Esta función de C siempre debería devolver falso, pero no lo hace
Hace mucho tiempo me encontré con una pregunta interesante en un foro y quiero saber la respuesta.
Considere la siguiente función C:
f1.c
#include <stdbool.h>
bool f1()
{
int var1 = 1000;
int var2 = 2000;
int var3 = var1 + var2;
return (var3 == 0) ? true : false;
}
Esto siempre debería regresar false
desde var3 == 3000
. La main
función se ve así:
C Principal
#include <stdio.h>
#include <stdbool.h>
int main()
{
printf( f1() == true ? "true\n" : "false\n");
if( f1() )
{
printf("executed\n");
}
return 0;
}
Dado que f1()
siempre debería devolver false
, uno esperaría que el programa imprimiera solo un valor falso en la pantalla. Pero después de compilarlo y ejecutarlo, también se muestra ejecutado :
$ gcc main.c f1.c -o test
$ ./test
false
executed
¿Porqué es eso? ¿Este código tiene algún tipo de comportamiento indefinido?
Nota: lo compilé con gcc (Ubuntu 4.9.2-10ubuntu13) 4.9.2
.
Como se señaló en otras respuestas, el problema es que lo usa gcc
sin opciones del compilador configuradas. Si hace esto, el valor predeterminado será lo que se llama "gnu90", que es una implementación no estándar del antiguo estándar C90 retirado de 1990.
En el antiguo estándar C90 había un defecto importante en el lenguaje C: si no se declaraba un prototipo antes de usar una función, el valor predeterminado sería int func ()
(donde ( )
significa "aceptar cualquier parámetro"). Esto cambia la convención de llamada de la función func
, pero no cambia la definición real de la función. Dado que el tamaño de bool
y int
son diferentes, su código invoca un comportamiento indefinido cuando se llama a la función.
Este comportamiento peligroso y sin sentido se solucionó en el año 1999, con el lanzamiento del estándar C99. Se prohibieron las declaraciones de funciones implícitas.
Desafortunadamente, GCC hasta la versión 5.xx todavía usa el antiguo estándar C de forma predeterminada. Probablemente no haya ninguna razón por la que quieras compilar tu código como algo que no sea C estándar. Por lo tanto, debes decirle explícitamente a GCC que debería compilar tu código como código C moderno, en lugar de una basura GNU no estándar de más de 25 años. .
Solucione el problema compilando siempre su programa como:
gcc -std=c11 -pedantic-errors -Wall -Wextra
-std=c11
le dice que haga un intento a medias de compilar de acuerdo con el estándar C (actual) (conocido informalmente como C11).-pedantic-errors
le dice que haga de todo corazón lo anterior y que proporcione errores de compilación cuando escriba código incorrecto que viole el estándar C.-Wall
significa darme algunas advertencias adicionales que sería bueno tener.-Wextra
significa darme algunas otras advertencias adicionales que sería bueno tener.
No tienes un prototipo declarado f1()
en main.c, por lo que está implícitamente definido como int f1()
, lo que significa que es una función que toma un número desconocido de argumentos y devuelve un archivo int
.
Si int
y bool
son de diferentes tamaños, esto dará como resultado un comportamiento indefinido . Por ejemplo, en mi máquina, int
son 4 bytes y bool
un byte. Dado que la función está definida para regresar bool
, coloca un byte en la pila cuando regresa. Sin embargo, dado que implícitamente se declara que regresa int
desde main.c, la función de llamada intentará leer 4 bytes de la pila.
Las opciones predeterminadas de los compiladores en gcc no le dirán que está haciendo esto. Pero si compilas con -Wall -Wextra
, obtendrás esto:
main.c: In function ‘main’:
main.c:6: warning: implicit declaration of function ‘f1’
Para solucionar este problema, agregue una declaración for f1
en main.c, antes de main
:
bool f1(void);
Tenga en cuenta que la lista de argumentos está establecida explícitamente en void
, lo que le indica al compilador que la función no toma argumentos, a diferencia de una lista de parámetros vacía que significa un número desconocido de argumentos. La definición f1
en f1.c también debería cambiarse para reflejar esto.