2010-04-14 8 views
12

Este es el código C que libera la memoria de una lista vinculada individualmente. Está compilado con Visual C++ 2008 y el código funciona como debería ser.Pregunta sobre los compiladores y cómo funcionan

/* Program done, so free allocated memory */ 
current = head; 
struct film * temp; 
temp = current; 
while (current != NULL) 
{ 
    temp = current->next; 
    free(current); 
    current = temp; 
} 

Pero también me encontré (incluso en un par de libros) mismo código escribe así:

/* Program done, so free allocated memory */ 
current = head; 
while (current != NULL) 
{ 
    free(current); 
    current = current->next; 
} 

Si puedo compilar el código con mis VC++ 2008, el programa se bloquea porque estoy primero liberando actual y luego asignando current-> next to current. Pero, obviamente, si compilo este código con algún otro compilador (por ejemplo, compilador que el autor del libro usó) el programa funcionará. Entonces la pregunta es, ¿por qué se compila este código con el trabajo de compilación específico? ¿Es porque ese compilador puso instrucciones en un archivo binario que recuerda la dirección de current-> next aunque liberé actual y mi VC++ no lo hace? Solo quiero entender cómo funcionan los compiladores.

+2

¿Qué libros serían esos? –

+4

@Neil, malos. –

+10

Cuéntanos el libro para evitarlo y recomendarlo. –

Respuesta

18

El segundo programa invoca un comportamiento indefinido. No es una diferencia en el compilador, sino una diferencia en la implementación de la biblioteca estándar C y la función free(). El compilador almacenará el puntero current como una variable local, pero no almacenará una copia de la memoria a la que hace referencia.

Cuando invoca free(), abandona la propiedad de la memoria apuntada por el puntero pasado a la función free(). Es posible que después de renunciar a la propiedad, los contenidos de la memoria apuntada sigan siendo razonables y sigan siendo ubicaciones de memoria válidas en el espacio de direcciones de su proceso. En consecuencia, es posible que el acceso a ellos parezca funcionar (tenga en cuenta que puede dañar la memoria de esta manera). Un puntero que no sea nulo y apunta a la memoria que ya ha sido cedida se conoce como dangling pointer y es increíblemente peligroso. El hecho de que parezca funcionar no significa que sea correcto.

Debo señalar también que es posible implementar free() de forma tal que capte estos errores, como usar una página por asignación diferente y desasignar la página cuando se llame a free() (para que la dirección de la memoria ya no es una dirección válida para ese proceso). Tales implementaciones son altamente ineficientes, pero algunas veces son utilizadas por ciertos compiladores cuando se encuentran en modo de depuración para detectar errores de puntero colgando.

+1

Probablemente sería más fácil poner la dirección de los últimos 4 libre en los registros DR0-DR3 y poner un punto de interrupción de lectura en todos ellos. – MSalters

3

El segundo ejemplo es código incorrecto; no debe estar haciendo referencia a current después de haber sido liberado. Esto parecerá funcionar en muchos casos pero es un comportamiento indefinido. Usar herramientas como valgrind eliminará errores como este.

Indique en qué libro (s) ha visto este ejemplo, porque es necesario corregirlo.

+0

http://bytes.com/topic/c/answers/212665-freeing-simple-linked-list C primer plus (todas las ediciones, 5º, 4º ...) – dontoo

+0

Gracias - Sabía que los libros que el indio Las universidades que usan la programación en C son bastante malas (Kanetkar, Balaguruswami, etc.), pero creo que el problema está más extendido. –

11

Después de hacer free(current), la memoria apuntada por current (donde current->next está almacenado) ha sido devuelta a la biblioteca C, por lo que ya no debe tener acceso a ella.

La biblioteca C puede cambiar el contenido de esa memoria en cualquier momento, lo que provocará la corrupción de current->next, pero también puede que no cambie una parte o la totalidad, especialmente tan pronto. Es por eso que funciona en algunos entornos, y no en otros.

Es como conducir a través de un semáforo en rojo. Algunas veces te saldrás con la tuya, pero a veces serás atropellado por un camión.

4

La mejor manera de descubrir cómo funcionan los compiladores es no preguntar cómo manejan el código no válido. Necesita leer un libro (más de uno, en realidad) sobre la compilación.Un buen lugar para comenzar sería consultar los recursos en Learning to write a compiler.

1

En realidad ese es el tiempo de ejecución C, no el compilador. De todos modos, este último código contiene un comportamiento indefinido, no lo hagas. Funcionó para alguien en alguna implementación, pero como ve, se cuelga mal en el suyo. También podría dañar algo silenciosamente.

La posible explicación de por qué lo último podría funcionar es que en alguna implementación free() no modifica el contenido del bloque y no devuelve el bloque de memoria al sistema operativo inmediatamente, por lo que quitar un puntero al bloque sigue siendo " legal "y los datos en el bloque todavía están intactos.

0

Es porque ese compilador puso instrucciones en el archivo binario que recuerda la dirección actual-> siguiente aunque liberé la corriente y mi VC++ no lo hace.

No lo creo.

Solo quiero entender cómo funcionan los compiladores.

Aquí se muestra un ejemplo con compilador GCC (no tengo VC++)

struct film { film* next; }; 

int main() { 
    film* current = new film(); 
    delete current; 

    return 0; 
} 

;Creation 
movl $4, (%esp) ;the sizeof(film) into the stack (4 bytes) 
call _Znwj  ;this line calls the 'new operator' 
        ;the register %eax now has the pointer 
        ;to the newly created object 

movl $0, (%eax) ;initializes the only variable in film 

;Destruction 
movl %eax, (%esp) ;push the 'current' point to the stack 
call _ZdlPv  ;calls the 'delete operator' on 'current' 

si se trataba de una clase y tiene un destructor, entonces se debería llamar antes de liberar el espacio del objeto ocupa en la memoria con el operador de eliminación.

Después de destruir el objeto y liberar su espacio de memoria, ya no puede hacer referencia actual-> siguiente de forma segura.