c gcc memory-management

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.