Conflicto entre un tutorial de Stanford y GCC
memory-management (5)
Compila tu código con gcc -Wall filename.c
Verás estas advertencias.
In function ''B'':
11:9: warning: variable ''a'' set but not used [-Wunused-but-set-variable]
In function ''A'':
6:11: warning: ''a'' is used uninitialized in this function [-Wuninitialized]
En c Impresión de variable no inicializada Lleva a Comportamiento no definido.
Sección 6.7.8 Inicialización de la norma C99 dice
Si un objeto que tiene una duración de almacenamiento automática no se inicializa explícitamente, su valor es indeterminado. Si un objeto que tiene una duración de almacenamiento estática no se inicializa explícitamente, entonces:
— if it has pointer type, it is initialized to a null pointer;
— if it has arithmetic type, it is initialized to (positive or unsigned) zero;
— if it is an aggregate, every member is initialized (recursively) according to these rules;
— if it is a union, the first named member is initialized (recursively) according to these rules.
Edit1
Como @Jonathon Reinhart Si deshabilita la optimización utilizando -O
marca gcc-O0
, es posible que obtenga la salida 5.
Pero esto no es en absoluto una buena idea, nunca lo use en el código de producción.
-Wuninitialized
esta es una de las advertencias importantes. Debería considerar esta -Wuninitialized
No debe deshabilitar u omitir esta advertencia que causa un daño enorme en la producción, como causar bloqueos mientras se ejecutan daemons.
Edit2
http://www.slideshare.net/olvemaudal/deep-c explicaron las diapositivas de http://www.slideshare.net/olvemaudal/deep-c Por qué el resultado es 5 / basura. Agregar esta información de esas diapositivas con modificaciones menores para hacer esta respuesta un poco más efectiva.
Caso 1: sin optimización
$ gcc -O0 file.c && ./a.out
5
Quizás este compilador tiene un grupo de variables nombradas que reutiliza. Por ejemplo, la variable a se usó y se liberó en B()
; luego, cuando A()
necesita un nombre entero, obtendrá la misma ubicación de memoria. Si cambia el nombre de la variable en B()
a, digamos b
, entonces no creo que obtenga 5
.
Caso 2: con optimización
Pueden ocurrir muchas cosas cuando se activa el optimizador. En este caso, supongo que la llamada a B()
se puede omitir, ya que no tiene ningún efecto secundario. Además, no me sorprendería si el A()
está subrayado en main()
, es decir, no hay una llamada de función. (Pero dado que A ()
tiene visibilidad del enlazador, el código del objeto para la función debe crearse aún en el caso de que otro archivo objeto desee vincularse con la función). De todos modos, sospecho que el valor impreso será algo más si optimizas el código.
gcc -O file.c && ./a.out
1606415608
¡Basura!
De acuerdo con this película (alrededor del minuto 38), si tengo dos funciones con los mismos vars locales, usarán el mismo espacio. Entonces, el siguiente programa, debería imprimir 5
. -1218960859
con los resultados de gcc
-1218960859
. ¿por qué?
El programa:
#include <stdio.h>
void A()
{
int a;
printf("%i",a);
}
void B()
{
int a;
a = 5;
}
int main()
{
B();
A();
return 0;
}
según lo solicitado, aquí está la salida del desensamblador:
0804840c <A>:
804840c: 55 push ebp
804840d: 89 e5 mov ebp,esp
804840f: 83 ec 28 sub esp,0x28
8048412: 8b 45 f4 mov eax,DWORD PTR [ebp-0xc]
8048415: 89 44 24 04 mov DWORD PTR [esp+0x4],eax
8048419: c7 04 24 e8 84 04 08 mov DWORD PTR [esp],0x80484e8
8048420: e8 cb fe ff ff call 80482f0 <printf@plt>
8048425: c9 leave
8048426: c3 ret
08048427 <B>:
8048427: 55 push ebp
8048428: 89 e5 mov ebp,esp
804842a: 83 ec 10 sub esp,0x10
804842d: c7 45 fc 05 00 00 00 mov DWORD PTR [ebp-0x4],0x5
8048434: c9 leave
8048435: c3 ret
08048436 <main>:
8048436: 55 push ebp
8048437: 89 e5 mov ebp,esp
8048439: 83 e4 f0 and esp,0xfffffff0
804843c: e8 e6 ff ff ff call 8048427 <B>
8048441: e8 c6 ff ff ff call 804840c <A>
8048446: b8 00 00 00 00 mov eax,0x0
804844b: c9 leave
804844c: c3 ret
804844d: 66 90 xchg ax,ax
804844f: 90 nop
En la función A
, la variable a
no se inicializa, imprimir su valor conduce a un comportamiento indefinido.
En algún compilador, la variable a
en A
y a
en B
están en la misma dirección, por lo que puede imprimir 5
, pero nuevamente, no puede confiar en un comportamiento indefinido.
Es un comportamiento indefinido . Una variable local no inicializada tiene un valor indeterminado, y su uso conducirá a un comportamiento indefinido.
Sí, sí, esto es un comportamiento indefinido , porque estás usando la variable sin inicializar 1 .
Sin embargo, en la arquitectura x86 2 , este experimento debería funcionar . El valor no se "borra" de la pila, y dado que no se inicializa en B()
, ese mismo valor debería estar allí, siempre que los marcos de la pila sean idénticos.
Me arriesgaría a adivinar que, dado que int a
no se usa dentro de void B()
, el compilador optimizó ese código y nunca se escribió un 5 en esa ubicación en la pila. Intente agregar un printf
en B()
también, simplemente puede funcionar.
Además, los indicadores del compilador, a saber, el nivel de optimización, también afectarán este experimento. Intente deshabilitar optimizaciones pasando -O0
a gcc.
Editar: acabo de compilar tu código con gcc -O0
(64-bit), y de hecho, el programa imprime 5, como se esperaría de alguien familiarizado con la pila de llamadas. De hecho, funcionó incluso sin -O0
. Una compilación de 32 bits puede comportarse de manera diferente.
Descargo de responsabilidad: ¡Nunca, nunca use algo así en código "real"!
1 - Hay un debate a below sobre si esto es oficialmente "UB", o simplemente impredecible.
2 - También x64, y probablemente cualquier otra arquitectura que use una pila de llamadas (al menos una con una MMU)
Echemos un vistazo a una razón por la cual no funcionó. Esto se ve mejor en 32 bits, por lo que compilaré con -m32
.
$ gcc --version
gcc (GCC) 4.7.2 20120921 (Red Hat 4.7.2-2)
$ gcc -m32 -O0 test.c
con $ gcc -m32 -O0 test.c
(optimizaciones desactivadas). Cuando ejecuto esto, imprime basura.
Mirando $ objdump -Mintel -d ./a.out
:
080483ec <A>:
80483ec: 55 push ebp
80483ed: 89 e5 mov ebp,esp
80483ef: 83 ec 28 sub esp,0x28
80483f2: 8b 45 f4 mov eax,DWORD PTR [ebp-0xc]
80483f5: 89 44 24 04 mov DWORD PTR [esp+0x4],eax
80483f9: c7 04 24 c4 84 04 08 mov DWORD PTR [esp],0x80484c4
8048400: e8 cb fe ff ff call 80482d0 <printf@plt>
8048405: c9 leave
8048406: c3 ret
08048407 <B>:
8048407: 55 push ebp
8048408: 89 e5 mov ebp,esp
804840a: 83 ec 10 sub esp,0x10
804840d: c7 45 fc 05 00 00 00 mov DWORD PTR [ebp-0x4],0x5
8048414: c9 leave
8048415: c3 ret
Vemos que en B
, el compilador reservó 0x10 bytes de espacio de pila e inicializó nuestra variable int a
en [ebp-0x4]
a 5.
En A
, sin embargo, el compilador colocó int a
en [ebp-0xc]
. ¡Entonces en este caso nuestras variables locales no terminaron en el mismo lugar! Al agregar una llamada printf()
en A
también hará que los marcos de pila para A
y B
sean idénticos, e imprima 55
.
Una cosa importante para recordar: ¡ nunca confíes en algo así y nunca uses esto en código real! Es solo algo interesante (que incluso no siempre es cierto), no es una característica o algo así. Imagínese tratando de encontrar errores producidos por ese tipo de "característica": pesadilla.
Por cierto. - C y C ++ están llenos de ese tipo de "características", aquí hay GRANDES presentaciones de diapositivas al respecto: http://www.slideshare.net/olvemaudal/deep-c Así que si quieres ver más "características" similares, entiende qué es bajo el capó y cómo funciona, solo mira esta presentación de diapositivas; no te arrepentirás y estoy seguro de que incluso la mayoría de los programadores experimentados de c / c ++ pueden aprender mucho de esto.