online c gcc

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.