Extraño comportamiento de los punteros en C++

Los siguientes resultados son muy interesantes y tengo dificultades para entenderlos. Básicamente tengo una clase que tiene un int:

class TestClass{
public:
    int test;
    TestClass() { test = 0; };
    TestClass(int _test) { test = _test; };
    ~TestClass() { /*do nothing*/ };
};

Una función de prueba que acepta un puntero de TestClass

void testFunction1(TestClass *ref){
    delete ref;
    TestClass *locTest = new TestClass();
    ref = locTest;
    ref->test = 2;
    cout << "test in testFunction1: " << ref->test << endl;
}

Esto es lo que estoy haciendo en main:

int main(int argc, _TCHAR* argv[])
{
    TestClass *testObj = new TestClass(1);
    cout << "test before: " << testObj->test << endl;
    testFunction1(testObj);
    cout << "test after: " << testObj->test << endl;
    return 0;
}

Esperaba que la salida fuera:

test before: 1
test in testFunction1: 2
test after: 1

Pero obtengo el siguiente resultado:

test before: 1
test in testFunction1: 2
test after: 2

¿Alguien puede explicar esto? Lo interesante es que cambiar testFunction1 a:

void testFunction1(TestClass *ref){
    //delete ref;
    TestClass *locTest = new TestClass();
    ref = locTest;
    ref->test = 2;
    cout << "test in testFunction1: " << ref->test << endl;
}

es decir, no elimino la referencia antes de señalarla a una nueva ubicación, obtengo el siguiente resultado:

test before: 1
test in testFunction1: 2
test after: 1

Realmente agradecería si alguien puede explicarme este extraño comportamiento. Gracias.

preguntado el 22 de mayo de 12 a las 17:05

6 Respuestas

Cuando accede al objeto después de eliminarlo, el comportamiento no está definido.

El comportamiento que ve se deriva del algoritmo de asignación de memoria en su sistema.

Es decir, después de eliminar su primer objeto de clase de prueba, asigna memoria para un nuevo objeto. El tiempo de ejecución simplemente reutiliza la memoria.

Para comprobar lo que está pasando, imprima los valores de los punteros:

void testFunction1(TestClass *ref){
    cout << ref << endl;
    delete ref;
    TestClass *locTest = new TestClass();
    cout << locTest << endl;
    ref = locTest;
    ref->test = 2;
    cout << "test in testFunction1: " << ref->test << endl;
}

contestado el 22 de mayo de 12 a las 19:05

Obtiene una copia del puntero al objeto en testFunction1(), por lo que cuando lo asigne, el valor original del puntero en main() no cambia

Además, está eliminando el objeto (llamando delete in testFunction1()) a la que el puntero original (en main()) está apuntando, pero como el valor en main() no está actualizado, está accediendo a un objeto no válido: el hecho de que pueda leer el valor que estableció en testFunction1(), es una coincidencia y no se puede confiar

El hecho de que hayas leído correctamente el valor original en el segundo caso (cuando no llamas delete) es porque el objeto original no se ha cambiado (se cambia uno nuevo en testFinction1 y el puntero a él en main es el mismo (como se explicó anteriormente) y el objeto todavía está vivo

contestado el 22 de mayo de 12 a las 19:05

Exactamente, y la pérdida de memoria está en testFunction1() ya que nunca obtienes la dirección de la memoria recién creada fuera de la función. - Brady

pero ¿por qué el valor del puntero en main se actualiza de 1 a 2 si estamos pasando una copia a testFunction1() - umair

@umair: Es un comportamiento indefinido. No tienes derecho a ninguna expectativa. - benjamin lindley

@umair El valor del puntero no cambia. Estás pensando en el puntero como si fuera el objeto. Imagina que tienes 3 baldes llenos de agua. Su puntero apunta al balde 2. Ahora vacíe y vuelva a llenar el balde 2 con aceite (testFunction1). Ahora, cuando el puntero original señala el balde, obtiene aceite, no agua. - sji

@sji - excepto en testFunction1 el OP crea un nuevo "cubo", por lo que aún obtiene "agua", no "aceite" (cuando el "Cubo" original no se destruye, eso es) - Attila

en este caso, acaba de recibir el nuevo objeto en la misma dirección que el anterior que eliminó.

en realidad, testObj se convirtió en un puntero colgante después de llamar a testFunction1.

void testFunction1(TestClass *ref){
    delete ref;
    TestClass *locTest = new TestClass();
    cout << "locTest = " << locTest << endl;
    ref = locTest;
    ref->test = 2;
    cout << "test in testFunction1: " << ref->test << endl;
}

int main(int argc, char * argv[])
{
    TestClass *testObj = new TestClass(1);
    cout << "test before: " << testObj->test << endl;
    cout << "testObg = " << testObj << endl;
    testFunction1(testObj);
    cout << "test after: " << testObj->test << endl;
    cout << "testObg = " << testObj << endl;
    return 0;
}

La salida es:

test before: 1
testObg = 0x511818
locTest = 0x511818
test in testFunction1: 2
test after: 2
testObg = 0x511818

contestado el 22 de mayo de 12 a las 18:05

Después de esta instrucción:

TestClass *testObj = new TestClass(1);

ha asignado una nueva área de memoria que contiene un TestClass objeto, cuya dirección (llamémoslo maddr) se almacena en testObj.

Ahora, esta instrucción:

cout << "test before: " << testObj->test << endl;

salidas 1, como se esperaba.

Inside testFunction1() tienes una variable local llamada ref, que es un puntero que contiene el valor maddr.

Cuando delete ref, usted desasigna el área de memoria que contiene un TestClass objeto, cuya dirección es maddr.

Luego asignas una nueva área de memoria:

TestClass *locTest = new TestClass();

y locTest contiene su dirección, llamémoslo m1addr.

Entonces usas ref para acceder al área de memoria en m1addr y cambiar el int test valor para 2.

Esta instrucción:

cout << "test in testFunction1: " << ref->test << endl;

salidas 2 como se esperaba.

Ahora, de vuelta a main, ha perdido algún controlador en el área que contiene un TestClass objeto cuya dirección es m1addr (es decir, está perdiendo memoria) y el área señalada por testObj ya no se asigna.

Cuando se utiliza testObj nuevamente, accede a un área de memoria que comienza en maddr, que ha sido limpiado. El efecto de acceder testObj->test es un comportamiento indefinido.

Lo que experimenta podría deberse al hecho de que cuando ejecuta su código maddr == m1addr, pero esto solo puede suceder por casualidad, no puedes confiar en esto.

contestado el 22 de mayo de 12 a las 18:05

Lo más probable es que el nuevo objeto (con un 2) se cree donde solía estar el antiguo objeto eliminado (con un 1). Su puntero original aún apunta a esa ubicación, por lo que cuando accede a él, accede accidentalmente al nuevo objeto (con un 2).

contestado el 22 de mayo de 12 a las 18:05

TestClass *ref en el parámetro para testFunction1 y TestClass *testObj en main son 2 punteros diferentes a lo mismo, aunque no son el mismo puntero. Si desea eliminar y reasignar un objeto dentro de una función/método, puede usar un puntero a un puntero como parámetro.

Como han mencionado otros, después de testfunction1 está accediendo a un objeto que se eliminó dentro de testfunction1 que, como también se mencionó, es un comportamiento indefinido. La memoria a la que apunta el puntero colgante se liberó durante la eliminación, pero es probable que el contenido siga allí hasta que la ubicación de la memoria se reasigne durante otra llamada a new. Por lo tanto, está utilizando espacio no asignado que podría sobrescribirse muy fácilmente en cualquier momento.

Espero que esto ayude

contestado el 22 de mayo de 12 a las 18:05

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.