online - gcc flags list
Esta funciĆ³n C siempre debe devolver falso, pero no (4)
Me topé con una pregunta interesante en un foro hace mucho tiempo 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 debe devolver
false
ya que
var3 == 3000
.
La función
main
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;
}
Como
f1()
siempre debe devolver
false
, uno esperaría que el programa imprima solo uno
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 usa
gcc
sin opciones de compilación establecidas.
Si hace esto, el valor predeterminado es 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 una falla importante en el lenguaje C: si no declaraba un prototipo antes de usar una función, sería por defecto
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.
Como el tamaño de
bool
e
int
son diferentes, su código invoca un comportamiento indefinido cuando se llama a la función.
Este comportamiento sin sentido peligroso se solucionó en el año 1999, con el lanzamiento del estándar C99. Las declaraciones de funciones implícitas fueron prohibidas.
Desafortunadamente, GCC hasta la versión 5.xx todavía usa el viejo estándar C por defecto. Probablemente no haya ninguna razón por la que deba compilar su código como algo más que el estándar C. Por lo tanto, debe decirle explícitamente a GCC que debe compilar su 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 poco entusiasta de compilar según el estándar C (actual) (informalmente conocido como C11). -
-pedantic-errors
le dice que haga de todo corazón lo anterior y que dé errores del compilador cuando escribe un código incorrecto que viola el estándar C. -
-Wall
significa darme algunas advertencias adicionales que podría ser bueno tener. -
-Wextra
significa darme otras advertencias adicionales que podrían ser buenas.
Compile con un comando como este:
gcc -Wall -Wextra -Werror -std=gnu99 -o main.exe main.c
Salida:
main.c: In function ''main'':
main.c:14:5: error: implicit declaration of function ''f1'' [-Werror=impl
icit-function-declaration]
printf( f1() == true ? "true/n" : "false/n");
^
cc1.exe: all warnings being treated as errors
Con tal mensaje, debe saber qué hacer para corregirlo.
Editar: después de leer un comentario (ahora eliminado), intenté compilar su código sin las banderas.
Bueno, esto me llevó a errores de enlazador sin advertencias del compilador en lugar de errores del compilador.
Y esos errores del enlazador son más difíciles de entender, por lo que incluso si
-std-gnu99
no es necesario, intente usar al menos
-Wall -Werror
que le ahorre mucho dolor en el culo.
Creo que es interesante ver dónde sucede realmente la falta de coincidencia de tamaño mencionada en la excelente respuesta de Lundin.
Si compila con
--save-temps
, obtendrá archivos de ensamblaje que puede ver.
Aquí está la parte donde
f1()
hace la comparación
== 0
y devuelve su valor:
cmpl $0, -4(%rbp)
sete %al
La parte de retorno es
sete %al
.
En las convenciones de llamadas x86 de C, los valores de retorno de 4 bytes o menos (que incluyen
int
y
bool
) se devuelven a través del registro
%eax
.
%al
es el byte más bajo de
%eax
.
Por lo tanto, los 3 bytes superiores de
%eax
quedan en un estado no controlado.
Ahora en
main()
:
call f1
testl %eax, %eax
je .L2
Esto comprueba si
todo el
%eax
es cero, porque cree que está probando un int.
Agregar una declaración de función explícita cambia
main()
a:
call f1
testb %al, %al
je .L2
que es lo que queremos
No tiene un prototipo declarado para
f1()
en main.c, por lo que se define implícitamente como
int f1()
, lo que significa que es una función que toma un número desconocido de argumentos y devuelve un
int
.
Si
int
y
bool
son de diferentes tamaños, esto dará como resultado
un comportamiento indefinido
.
Por ejemplo, en mi máquina,
int
es de 4 bytes y
bool
es de un byte.
Como la función está
definida
para devolver
bool
, coloca un byte en la pila cuando regresa.
Sin embargo, dado que se
declara implícitamente
que devuelve
int
desde main.c, la función de llamada intentará leer 4 bytes de la pila.
Las opciones de compiladores predeterminadas en gcc no le dirán que está haciendo esto.
Pero si compila con
-Wall -Wextra
, obtendrá esto:
main.c: In function ‘main’:
main.c:6: warning: implicit declaration of function ‘f1’
Para solucionar esto, agregue una declaración para
f1
en main.c, antes de
main
:
bool f1(void);
Tenga en cuenta que la lista de argumentos se establece explícitamente en
void
, lo que le dice al compilador que la función no acepta argumentos, en oposición a una lista de parámetros vacía que significa un número desconocido de argumentos.
La definición
f1
en f1.c también debe cambiarse para reflejar esto.