resueltos memoria ejercicios directa correspondencia cache c gcc memory-management operating-system

ejercicios - truco de diseño de memoria



correspondencia directa memoria cache (3)

He estado siguiendo este curso en youtube y estaba hablando de cómo algunos programadores pueden usar allí el conocimiento de cómo se establece la memoria para hacer cosas inteligentes. Uno de los ejemplos en la conferencia fue algo así

#include <stdio.h> void makeArray(); void printArray(); int main(){ makeArray(); printArray(); return 0; } void makeArray(){ int array[10]; int i; for(i=0;i<10;i++) array[i]=i; } void printArray(){ int array[10]; int i; for(i=0;i<10;i++) printf("%d/n",array[i]); }

la idea es que mientras las dos funciones tengan el mismo tamaño de registro de activación en el segmento de la pila, funcionará e imprimirá números del 0 al 9 ... pero en realidad imprime algo así

134520820 -1079626712 0 1 2 3 4 5 6 7

siempre hay esos dos valores en la mendicidad ... ¿alguien puede explicar eso? iam usando gcc en linux

la URL de la conferencia exacta a partir de las 5:15


Nunca, nunca, jamás, jamás, jamás, hagan algo como esto. No funcionará de manera confiable. Obtendrás errores extraños. Está lejos de ser portátil.

Formas en que puede fallar:

.1. El compilador agrega código extra y oculto

DevStudio, en modo de depuración, agrega llamadas a funciones que controlan la pila para detectar errores en la pila. Estas llamadas sobrescribirán lo que estaba en la pila, perdiendo así sus datos.

.2. Alguien agrega una llamada Entrar / Salir

Algunos compiladores permiten que el programador defina funciones para llamar a la entrada de funciones y a la salida de funciones. Al igual que (1) estos usan espacio de pila y sobrescribirán lo que ya está allí, perdiendo datos.

.3. Interrupciones

En main (), si obtiene una interrupción entre las llamadas a makeArray y printArray, perderá sus datos. Lo primero que ocurre al procesar una interrupción es guardar el estado de la CPU. Esto generalmente implica presionar los registros de la CPU y las banderas en la pila, y sí, lo adivinaste, sobrescribes tus datos.

.4. Los compiladores son inteligentes

Como ha visto, la matriz en makeArray está en una dirección diferente a la de printArray. El compilador ha colocado sus variables locales en diferentes posiciones en la pila. Utiliza un algoritmo complejo para decidir dónde colocar la variable: en la pila, en un registro, etc., y realmente no vale la pena tratar de descubrir cómo lo hace el compilador, ya que la próxima versión del compilador podría hacerlo de otra manera.

En resumen, este tipo de "trucos inteligentes" no son trucos y ciertamente no son inteligentes. No perderá nada al declarar la matriz en main y pasando una referencia / puntero a ella en las dos funciones. Las pilas son para almacenar variables locales y direcciones de retorno de funciones. Una vez que los datos salen del alcance (es decir, la parte superior de la pila se reduce al pasar los datos), entonces los datos se pierden efectivamente: cualquier cosa puede sucederle.

Para ilustrar este punto más, sus resultados probablemente serían diferentes si tuviera diferentes nombres de funciones (estoy adivinando aquí, OK).


Probablemente, GCC genera código que no envía los argumentos a la pila cuando llama a una función, sino que asigna espacio extra en la pila. Los argumentos para su llamada a la función ''printf'', "% d / n" y array [i] toman 8 bytes en la pila, el primer argumento es un puntero y el segundo es un número entero. Esto explica por qué hay dos enteros que no se imprimen correctamente.


Lo siento, pero no hay absolutamente nada inteligente sobre ese fragmento de código y las personas que lo usan son muy tontas.

Apéndice:

O, a veces, solo algunas veces, muy inteligente. Después de haber visto el video vinculado en la actualización de la pregunta, este no era un código malicioso que rompía las reglas. Este chico entendió lo que estaba haciendo bastante bien.

Requiere una comprensión profunda del código subyacente generado y puede romperse fácilmente (como se menciona y se ve aquí) si su entorno cambia (como compiladores, arquitecturas, etc.).

Pero, si tiene ese conocimiento, probablemente pueda salirse con la suya. No es algo que sugiriera a nadie más que a un veterano, pero puedo ver que tiene su lugar en situaciones muy limitadas y, para ser sincero, sin duda, en ocasiones he sido algo más ... pragmático ... de lo que debería haberlo hecho. estado en mi propia carrera :-)

Ahora volvamos a tu programación habitual ...

No es portátil entre arquitecturas, compiladores, versiones de compiladores y, probablemente, incluso niveles de optimización dentro del mismo release de un compilador, además de ser un comportamiento indefinido (lectura de variables no inicializadas).

La mejor opción para comprender es examinar el código de ensamblador generado por el compilador.

Pero su mejor apuesta en general es olvidarse de eso y codificar según el estándar.

Por ejemplo, esta transcripción muestra cómo gcc puede tener un comportamiento diferente en diferentes niveles de optimización:

pax> gcc -o qq qq.c ; ./qq 0 1 2 3 4 5 6 7 8 9 pax> gcc -O3 -o qq qq.c ; ./qq 1628373048 1629343944 1629097166 2280872 2281480 0 0 0 1629542238 1629542245

En el alto nivel de optimización de gcc (lo que me gusta llamar su nivel de optimización insana), esta es la función makeArray . Básicamente se descubrió que la matriz no se usa y, por lo tanto, optimizó su inicialización fuera de existencia.

_makeArray: pushl %ebp ; stack frame setup movl %esp, %ebp ; heavily optimised function popl %ebp ; stack frame tear-down ret ; and return

De hecho, estoy un poco sorprendido de que gcc incluso haya dejado allí el trozo de función.

Actualización: como señala Nicholas Knight en un comentario, la función permanece, ya que debe estar visible para el enlazador, lo que hace que la función sea estática da como resultado que gcc elimine el stub también.

Si comprueba el código de ensamblador en el nivel de optimización 0 a continuación, da una pista (no es el motivo real, consulte más abajo). Examine el siguiente código y verá que la configuración del marco de pila es diferente para las dos funciones a pesar de que tienen exactamente los mismos parámetros pasados ​​y las mismas variables locales:

subl $48, %esp ; in makeArray subl $56, %esp ; in printArray

Esto se debe a que printArray asigna un espacio extra para almacenar la dirección de la printf formato printf y la dirección del elemento de la matriz, cuatro bytes cada uno, que representa la diferencia de ocho bytes (dos valores de 32 bits).

Esa es la explicación más probable de que su matriz en printArray() esté desactivada por dos valores.

Aquí están las dos funciones en el nivel de optimización 0 para su disfrute :-)

_makeArray: pushl %ebp ; stack fram setup movl %esp, %ebp subl $48, %esp movl $0, -4(%ebp) ; i = 0 jmp L4 ; start loop L5: movl -4(%ebp), %edx movl -4(%ebp), %eax movl %eax, -44(%ebp,%edx,4) ; array[i] = i addl $1, -4(%ebp) ; i++ L4: cmpl $9, -4(%ebp) ; for all i up to and including 9 jle L5 ; continue loop leave ret .section .rdata,"dr" LC0: .ascii "%d/12/0" ; format string for printf .text _printArray: pushl %ebp ; stack frame setup movl %esp, %ebp subl $56, %esp movl $0, -4(%ebp) ; i = 0 jmp L8 ; start loop L9: movl -4(%ebp), %eax ; get i movl -44(%ebp,%eax,4), %eax ; get array[i] movl %eax, 4(%esp) ; store array[i] for printf movl $LC0, (%esp) ; store format string call _printf ; make the call addl $1, -4(%ebp) ; i++ L8: cmpl $9, -4(%ebp) ; for all i up to and including 9 jle L9 ; continue loop leave ret

Actualización: como señala Roddy en un comentario. esa no es la causa de su problema específico ya que, en este caso, la matriz está realmente en la misma posición en la memoria ( %ebp-44 con %ebp siendo el mismo en las dos llamadas). Lo que estaba tratando de señalar es que dos funciones con la misma lista de argumentos y los mismos parámetros locales no necesariamente terminan con el mismo diseño de marco de pila.

Todo lo que tomaría sería que printArray intercambiara la ubicación de sus variables locales (incluidos los temporales no creados explícitamente por el desarrollador) y usted tendría este problema.