2010-11-30 21 views
8

¿Por qué funciona eso?usando memoria sin asignar sin error?

#include <iostream> 
using namespace std; 

int main() { 
    float* tab[3]; 

    int i = 0; 
    while(i < 3) { 
     tab[i] = new float[3-i]; 
     i++; 
    } 

    cout << tab[2][7] << endl; 
    tab[2][7] = 6.87; 
    cout << tab[2][7] << endl; 

    i = 0; 
    while(i < 3) 
     delete[] tab[i]; 
} 

mientras que este no?

#include <iostream> 
using namespace std; 

int main() { 
    float* tab = new float[3]; 

    cout << tab[7] << endl; 
    tab[7] = 6.87; 
    cout << tab[7] << endl; 

    delete[] tab; 
} 

He intentado tanto programas en Windows XP con MS VS 2008, tanto compilado sin errores y el primero que corrió sin ningún error. El segundo hizo aparecer una ventana de error, sin embargo no puedo recordarlo y no puedo reproducir (no tengo acceso a Windows en este momento).

Los probé también en Linux (Kubuntu 10.10 con paquete kernel precompilado versión 2.6.35.23.25) con g ++ y ambos compilan y ejecutan sin ningún error.

¿Por qué? ¿No debería haber ventanas emergentes con algo así como "Acceso incorrecto a la memoria no asignada"?

Sé que debería (y, afortunadamente, lo hace) compilar sin errores, pero pensé que no debería funcionar sin ellos ... ¿Y por qué el segundo ejemplo comete errores en Windows y no en Linux?

Respuesta

9

El uso de la memoria no asignada da como resultado un comportamiento indefinido. No puede tener expectativas de lo que sucederá cuando haga esto, incluso en el mismo sistema y compilador, y mucho menos a través de diferentes combinaciones de hardware y compilador.

El programa podría bloquearse inmediatamente, podría funcionar por un tiempo y luego fallar más tarde, incluso podría funcionar perfectamente.

Sin embargo, acceder a la memoria que no le pertenece es siempre un error de programación. No piense en la apariencia de una operación correcta ya que "a veces funciona", piense en ello como "Realmente tuve mala suerte y mi error no aparece rápidamente".

5

Ambos hacen acceso de matriz fuera de límites: tiene una matriz de 3 punteros flotantes y está accediendo a la 8va matriz. Esto está destinado a bloquearse.

Sin embargo, a diferencia de Java u otros lenguajes administrados, no hay una comprobación de límites explícita para cada acceso a la matriz (ya que el costo de rendimiento es prohibitivo). Entonces, el único control de límites que tiene es su MMU. Si termina accediendo a la memoria que no pertenece a su aplicación, se bloqueará. Si accedes a la memoria que no está asignada pero que aún es parte de tu proceso (podría ser una palabra de guardia, por ejemplo), tendrá éxito silenciosamente. Esta es una gran receta para errores muy difíciles de rastrear.

La comprobación de los límites es la clave. Hazlo siempre que puedas.

1

En el primer ejemplo, la pestaña [2] tiene un valor que apunta a la memoria válida. la pestaña [2] +7 no está asignada, pero podría ser. Sin seg-fault.

En el segundo, la pestaña [7] no tiene ningún valor ... son bits aleatorios (posiblemente ceros, o 0xDEADBEEF o cualquier valor que haya al final). Esto casi seguro no apunta a la memoria que es válida para que esta aplicación tenga acceso. Por lo tanto: boom.

0

La protección de acceso a la memoria no es muy fina. Cuando asigna parte de la memoria, obtiene una página completa de memoria asignada a su programa. Cuando intenta acceder a esa memoria extra, es probable que tenga éxito, pero también es probable que ejecute otra memoria asignada a su programa.

Esta es la razón por la que los desbordamientos del buffer funcionan como un ataque. En muchos casos es predecible para qué se utiliza esa memoria extra después de su matriz. Si puedo controlar lo que colocas allí, puedo sobrescribir datos que no quieres que sobrescriba. Si puedo sobrescribir su pila de llamadas, entonces puedo ejecutar cualquier código que desee en el contexto de su proceso. Si se trata de un servicio que se ejecuta como un usuario administrador, entonces tengo una escalada de privilegios local. Si este es un servicio de Internet de algún tipo, entonces tengo un ataque de ejecución remota.

Su mejor opción es trabajar con estructuras más robustas como std :: vector a menos que tenga un propósito específico para usar matrices. (Y aun así, es posible que pueda get away with vectors).

+0

Incluso entonces std :: vector limita las comprobaciones en compilaciones de depuración (afortunadamente!) –

+0

A menos que use 'vector :: at()', o trabaje con iteradores en lugar de índices. – Eclipse

+0

En realidad, este también es un error. En primer lugar, no existen las "páginas" en el estándar y no todas las implementaciones funcionarán de la manera en que usted afirma que lo hacen en ese sentido. En segundo lugar, solo puede sobrescribir la pila (en sistemas que incluso usan una) cuando la matriz se encuentra en ella. La memoria asignada desde la tienda gratuita no está en la pila. El tipo de ataque del que hablas ocurre cuando haces algo como 'void f() {char buf [SZ]; obtiene (buf); } ', que no es el problema del OP. –

7

Mientras que las otras respuestas, a excepción de las de Mark, no son incorrectas, tampoco son exactamente correctas. Lo que está haciendo al acceder a los datos después del final de lo que ha asignado explícitamente en su programa está causando un "comportamiento indefinido". Puede hacer cualquier cosa, incluido el "trabajo".

La respuesta de Steve no existía cuando comencé a escribir esto.

+2

+1 publicación simultánea también da como resultado un comportamiento indefinido, fyi –

+0

¿Qué pasa con el mío? –

Cuestiones relacionadas