¿Cómo puede ser? ¿No es inaccesible la memoria de una variable local fuera de su función?
Usted alquila una habitación de hotel. Pones un libro en el cajón superior de la mesita de noche y te duermes. Echa un vistazo a la mañana siguiente, pero "olvida" devolver la clave. ¡Robas la llave!
Una semana después, regresas al hotel, no te registras, te escabulles a tu antigua habitación con tu llave robada y miras en el cajón. Tu libro sigue ahí. ¡Asombroso!
¿Como puede ser? ¿No son inaccesibles los contenidos del cajón de una habitación de hotel si no ha alquilado la habitación?
Bueno, obviamente, ese escenario puede suceder en el mundo real sin problemas. No hay una fuerza misteriosa que haga que tu libro desaparezca cuando ya no estés autorizado para estar en la habitación. Tampoco hay una fuerza misteriosa que te impide entrar a una habitación con una llave robada.
La administración del hotel no está obligada a eliminar su libro. No hiciste un contrato con ellos que dijera que si dejas cosas atrás, lo destrozarán por ti. Si vuelve a ingresar ilegalmente a su habitación con una llave robada para recuperarla, no se requiere que el personal de seguridad del hotel lo atrape a escondidas. No hizo un contrato con ellos que dijera "si trato de colarse nuevamente más tarde, debes detenerme ". Por el contrario, firmó un contrato con ellos que decía "Prometo no volver a mi habitación más tarde", un contrato que rompió .
En esta situación puede pasar cualquier cosa . El libro puede estar allí, tienes suerte. El libro de otra persona puede estar allí y el suyo puede estar en el horno del hotel. Alguien podría estar allí justo cuando entras, rompiendo tu libro en pedazos. El hotel podría haber eliminado la mesa y el libro por completo y reemplazarlo con un armario. Todo el hotel podría estar a punto de ser demolido y reemplazado por un estadio de fútbol, y morirás en una explosión mientras te escabulles.
No sabes lo que va a pasar; cuando el check out del hotel y robaron una tecla para usar ilegalmente después, le dio el derecho a vivir en un mundo predecible, seguro, ya que optó por romper las reglas del sistema.
C ++ no es un lenguaje seguro . Alegremente te permitirá romper las reglas del sistema. Si intentas hacer algo ilegal y tonto como regresar a una habitación en la que no estás autorizado a entrar y hurgar en un escritorio que tal vez ya no esté allí, C ++ no te detendrá. Los lenguajes más seguros que C ++ resuelven este problema al restringir su poder, al tener un control mucho más estricto sobre las teclas, por ejemplo.
ACTUALIZAR
Santo cielo, esta respuesta está recibiendo mucha atención. (No estoy seguro de por qué, lo consideré solo una pequeña analogía "divertida", pero lo que sea).
Pensé que podría ser pertinente actualizar esto un poco con algunas ideas más técnicas.
Los compiladores están en el negocio de generar código que gestiona el almacenamiento de los datos manipulados por ese programa. Hay muchas formas diferentes de generar código para administrar la memoria, pero con el tiempo se han arraigado dos técnicas básicas.
El primero es tener algún tipo de área de almacenamiento de "larga vida" donde la "vida útil" de cada byte en el almacenamiento, es decir, el período de tiempo cuando está asociado de manera válida con alguna variable de programa, no se puede predecir fácilmente con anticipación de tiempo. El compilador genera llamadas en un "administrador de almacenamiento dinámico" que sabe cómo asignar dinámicamente el almacenamiento cuando es necesario y reclamarlo cuando ya no es necesario.
El segundo método es tener un área de almacenamiento "de corta duración" donde la vida útil de cada byte sea bien conocida. Aquí, las vidas siguen un patrón de "anidación". La más longeva de estas variables de corta duración se asignará antes que cualquier otra variable de corta duración, y se liberará al final. Las variables de vida más corta se asignarán después de las de vida más larga y se liberarán antes que ellas. La vida útil de estas variables de vida más corta está "anidada" dentro de la vida de las de vida más larga.
Las variables locales siguen el último patrón; cuando se ingresa un método, sus variables locales cobran vida. Cuando ese método llama a otro método, las variables locales del nuevo método cobran vida. Estarán muertos antes de que las variables locales del primer método estén muertas. El orden relativo de los comienzos y finales de las vidas de los almacenamientos asociados con las variables locales se puede calcular con anticipación.
Por esta razón, las variables locales generalmente se generan como almacenamiento en una estructura de datos de "pila", porque una pila tiene la propiedad de que lo primero que se empuje será lo último que se extraiga.
Es como si el hotel decidiera alquilar habitaciones secuencialmente, y no se puede retirar hasta que todos con un número de habitación superior al que usted haya retirado.
Así que pensemos en la pila. En muchos sistemas operativos, obtienes una pila por subproceso y la pila se asigna a un cierto tamaño fijo. Cuando llamas a un método, las cosas se colocan en la pila. Si luego pasa un puntero a la pila fuera de su método, como lo hace el póster original aquí, eso es solo un puntero al medio de un bloque de memoria de un millón de bytes completamente válido. En nuestra analogía, sales del hotel; cuando lo haces, acabas de salir de la habitación ocupada con el número más alto. Si nadie más se registra después de usted y regresa a su habitación ilegalmente, se garantiza que todas sus cosas aún estarán allí en este hotel en particular .
Usamos pilas para tiendas temporales porque son realmente baratas y fáciles. No se requiere una implementación de C ++ para usar una pila para el almacenamiento de locales; podría usar el montón. No lo hace, porque eso haría que el programa fuera más lento.
No se requiere una implementación de C ++ para dejar intacta la basura que dejaste en la pila para que puedas volver a buscarla más tarde ilegalmente; Es perfectamente legal que el compilador genere código que vuelva a cero todo en la "habitación" que acaba de desocupar. No lo hace porque, de nuevo, eso sería costoso.
No se requiere una implementación de C ++ para garantizar que cuando la pila se reduce lógicamente, las direcciones que solían ser válidas todavía se asignan a la memoria. La implementación puede decirle al sistema operativo "hemos terminado de usar esta página de pila ahora. Hasta que yo diga lo contrario, emita una excepción que destruya el proceso si alguien toca la página de pila previamente válida". Nuevamente, las implementaciones en realidad no hacen eso porque es lento e innecesario.
En cambio, las implementaciones le permiten cometer errores y salirse con la suya. La mayor parte del tiempo Hasta que un día algo realmente horrible sale mal y el proceso explota.
Esto es problemático Hay muchas reglas y es muy fácil romperlas accidentalmente. Ciertamente tengo muchas veces. Y lo que es peor, el problema a menudo solo aparece cuando se detecta que la memoria está corrupta en miles de millones de nanosegundos después de que ocurrió la corrupción, cuando es muy difícil averiguar quién la echó a perder.
Más idiomas seguros para la memoria resuelven este problema al restringir su potencia. En C # "normal" simplemente no hay forma de tomar la dirección de un local y devolverla o almacenarla para más adelante. Puede tomar la dirección de un local, pero el idioma está ingeniosamente diseñado para que sea imposible usarlo después de la vida útil de los fines locales. Para tomar la dirección de un local y devolverla, debe poner el compilador en un modo especial "inseguro" y poner la palabra "inseguro" en su programa, para llamar la atención sobre el hecho de que probablemente esté haciendo algo peligroso que podría estar rompiendo las reglas.
Para más lectura:
address of local variable ‘a’ returned
; espectáculos valgrindInvalid write of size 4 [...] Address 0xbefd7114 is just below the stack ptr