#include <iostream> #include <sstream> using namespace std; unsigned int* theFunction(unsigned int b) { unsigned int a = 33 + b; return &a; } std::string getMessage(int a) { string bb = "El valor de la variable a es: "; stringstream ss; ss << bb << a << "\n"; return ss.str(); } int main() { int mi_variable = 40; unsigned int* a = theFunction(mi_variable ); if(*a > 55) { string msg( getMessage(*a)); cout << msg << endl; } cout << "El resultado es "<< 31 + *a << endl; system("PAUSE"); return EXIT_SUCCESS; }
Si lo compilas y lo pruebas no fallará, pero si le cambias e valor a mi_variable y juegas un poco con su valor al final fallará algo. Pero que ha pasado?
Cuando un programa un programa se inicia, reserva un trozo de memoria que se llama “pila de programa” (programa stack). Para que se usa? Cada vez que un programa llama a una función, el programa usa un trozo de memoria contigua (llamado stack frame), y en ella coloca lo que necesita para que la función se ejecute: por un lado alli pone la direccion de retorno, o sea, la dirección donde el programa debe ir cuando se acabe la función. Por otro lado reserva espacio para las variables que se usan en la función. En tercer lugar, salva el contenido de los registros del procesador que sean relevantes. Por ejemplo, en la siguiente función:
int f() { unsigned int a = 0; double b = 1.00; g(); return a; }el sistema reservaria un espacio para guardar la dirección de retorno (seguramente una palabra de memoria, de 32bits en SO de 32bits), otra para palabra para el unsigned int y dos palabras para el double. Además tambien guardaria los registros necesarios para continuar la ejecución del código que llama a f() una vez finalice. Que pasa con g(), pues que el sistema vuelve a usar otro trozo de memoria de la pila para ejecutar g(). Cuando acaba g() vuelve a f().
El valor que retorna f() se guarda en un registro para que esté alli cuando el código que llamó a f() se continúe ejecutando. Esta estructura es muy conveniente para el sistema. Por un lado no tiene que buscar un trozo de memoria conveniente sino que simplemente usa el siguiente al que tiene. Además, cuando acaba con la función simplemente abandona el trozo de memoria usado sin hacer ninguna limpieza, con lo que se gana velocidad y simplicidad a partes iguales.
Entonces, que ha pasado en el código del principio?
En el momento en que se sale de la función, esa zona de la memoria queda “abandonada” pero el valor de “a” no se pierde por que el sistema no hace ninguna limpieza. Si mi_variable es menor que 22, no pasa nada por que no entramos en el “if”, y por tanto no llamamos a la función getMessage. Asi que el valor final es correcto. En cambio, a partir de 22, entramos en el if y por tanto llamo a getMessage. Eso provoca que el sistema use el espacio contiguo de memoria, donde estaba el valor de a, y sobreescribe, “pisando” el valor de “a”.
Lo peor de todo es que no da ninguna excepción (en el momento de la compilación tira un warning que, como siempre, no deberías ignorar), pero nos mostrará el valor que tenga la memoria en ese momento que puede ser cualquiera, provocando un bug que puede llegar a ser escurridizo como él solo. Asi que cuando juegues con c++, recuerda que un gran poder conlleva una gran responsabilidad (o diversión, siempre que te guste cazar bugs, claro :) )
Espero que con esta entrada hayas entendido algo más sobre la pila. En otra entrada hablaremos un poco sobre el heap, o montón.
No hay comentarios:
Publicar un comentario