¿Se puede acceder a la memoria de una variable local fuera de su alcance?


1030

tengo el siguiente código.

#include <iostream>

int * foo()
{
    int a = 5;
    return &a;
}

int main()
{
    int* p = foo();
    std::cout << *p;
    *p = 8;
    std::cout << *p;
}

¡Y el código se está ejecutando sin excepciones de tiempo de ejecución!

La salida fue 58

¿Cómo puede ser? ¿No es inaccesible la memoria de una variable local fuera de su función?


14
esto ni siquiera se compilará como es; si arregla el negocio no conforme, gcc aún lo advertirá address of local variable ‘a’ returned; espectáculos valgrindInvalid write of size 4 [...] Address 0xbefd7114 is just below the stack ptr
sehe

76
@Serge: En mi juventud, una vez trabajé en un código de anillo cero un poco complicado que se ejecutaba en el sistema operativo Netware que implicaba moverse ingeniosamente alrededor del puntero de la pila de una manera que no estaba sancionada exactamente por el sistema operativo. Sabría cuándo cometí un error porque a menudo la pila terminaría solapando la memoria de la pantalla y podría ver cómo los bytes se escriben directamente en la pantalla. No puedes salirte con la tuya en estos días.
Eric Lippert

23
jajaja Necesitaba leer la pregunta y algunas respuestas antes de comprender dónde está el problema. ¿Es realmente una pregunta sobre el alcance de acceso de la variable? Ni siquiera usa 'a' fuera de su función. Y eso es todo lo que hay que hacer. Lanzar algunas referencias de memoria es un tema totalmente diferente del alcance variable.
erikbwork

10
La respuesta dupe no significa pregunta dupe. Muchas de las preguntas engañosas que la gente propuso aquí son preguntas completamente diferentes que se refieren al mismo síntoma subyacente ... pero el interrogador sabe cómo saberlo, por lo que deben permanecer abiertas. Cerré un viejo engañado y lo fusioné con esta pregunta que debería permanecer abierta porque tiene una muy buena respuesta.
Joel Spolsky

16
@Joel: Si la respuesta aquí es buena, debería fusionarse con preguntas más antiguas , de las cuales esto es un engaño, no al revés. Y esta pregunta es, de hecho, un engaño de las otras preguntas propuestas aquí y luego algunas (a pesar de que algunas de las propuestas son más adecuadas que otras). Tenga en cuenta que creo que la respuesta de Eric es buena. (De hecho, marqué esta pregunta por combinar las respuestas en una de las preguntas más antiguas para salvar las preguntas más antiguas)
Sbi

Respuestas:


4802

¿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:

  • ¿Qué pasa si C # permitió la devolución de referencias? Casualmente, ese es el tema de la publicación del blog de hoy:

    https://ericlippert.com/2011/06/23/ref-returns-and-ref-locals/

  • ¿Por qué usamos pilas para administrar la memoria? ¿Los tipos de valor en C # siempre se almacenan en la pila? ¿Cómo funciona la memoria virtual? Y muchos más temas sobre cómo funciona el administrador de memoria C #. Muchos de estos artículos también están relacionados con los programadores de C ++:

    https://ericlippert.com/tag/memory-management/


56
@muntoo: Desafortunadamente, no es como si el sistema operativo hiciera sonar una sirena de advertencia antes de que se descargue o desasigne una página de memoria virtual. Si está jugando con esa memoria cuando ya no la posee, el sistema operativo está perfectamente dentro de sus derechos para eliminar todo el proceso cuando toca una página desasignada. ¡Auge!
Eric Lippert

83
@ Kyle: Solo los hoteles seguros hacen eso. Los hoteles inseguros obtienen ganancias de ganancias cuantificables al no tener que perder tiempo en las teclas de programación.
Alexander Torstling

498
@cyberguijarro: Que C ++ no es seguro para la memoria es simplemente un hecho. No es "golpear" nada. Si hubiera dicho, por ejemplo, "C ++ es una mezcla horrible de características demasiado complejas y poco especificadas que se acumulan sobre un modelo de memoria frágil y peligroso y estoy agradecido cada día que ya no trabajo en él por mi propia cordura", eso sería bashing C ++. Señalar que no es seguro para la memoria es explicar por qué el póster original está viendo este problema; es responder la pregunta, no editorializar.
Eric Lippert

50
Hablando estrictamente, la analogía debería mencionar que la recepcionista del hotel estaba muy feliz de que llevaras la llave contigo. "Oh, ¿te importa si me llevo esta llave?" "Adelante. ¿Por qué me importaría? Solo trabajo aquí". No se vuelve ilegal hasta que intentes usarlo.
philsquared

140
Por favor, al menos considere escribir un libro algún día. Lo compraría incluso si fuera solo una colección de publicaciones de blog revisadas y ampliadas, y estoy seguro de que también lo haría mucha gente. Pero un libro con sus pensamientos originales sobre diversos asuntos relacionados con la programación sería una gran lectura. Sé que es increíblemente difícil encontrar el tiempo para ello, pero considera escribir uno.
Dyppl

276

Lo que estás haciendo aquí es simplemente leer y escribir en la memoria que solía ser la dirección a. Ahora que está fuera foo, es solo un puntero a un área de memoria aleatoria. Sucede que, en su ejemplo, esa área de memoria existe y nada más la está usando en este momento. No se rompe nada al continuar usándolo, y nada más lo ha sobrescrito todavía. Por lo tanto, el 5sigue ahí. En un programa real, esa memoria se reutilizaría casi de inmediato y se rompería algo al hacer esto (¡aunque los síntomas pueden no aparecer hasta mucho más tarde!)

Cuando regresa foo, le dice al sistema operativo que ya no está usando esa memoria y que puede reasignarse a otra cosa. Si tienes suerte y nunca se reasigna, y el sistema operativo no te atrapa usándolo de nuevo, entonces te saldrás con la mentira. Lo más probable es que termines escribiendo sobre cualquier otra cosa que termine con esa dirección.

Ahora, si te estás preguntando por qué el compilador no se queja, probablemente sea porque foofue eliminado por la optimización. Por lo general, te advertirá sobre este tipo de cosas. C asume que sabe lo que está haciendo, y técnicamente no ha violado el alcance aquí (no hay referencia a así mismo fuera de foo), solo las reglas de acceso a la memoria, que solo activan una advertencia en lugar de un error.

En resumen: esto no suele funcionar, pero a veces lo hará por casualidad.


152

Porque el espacio de almacenamiento aún no estaba pisoteado. No cuentes con ese comportamiento.


1
Hombre, esa fue la espera más larga para un comentario ya que "¿Qué es la verdad?" Tal vez fue la Biblia de Gedeón en el cajón de ese hotel. ¿Y qué les pasó, de todos modos? Tenga en cuenta que ya no están presentes, al menos en Londres. Supongo que según la legislación de Igualdad, necesitarías una biblioteca de tratados religiosos.
Rob Kent

Podría haber jurado que escribí eso hace mucho tiempo, pero apareció recientemente y descubrí que mi respuesta no estaba allí. Ahora tengo que averiguar tus alusiones anteriores, ya que espero que me divierta cuando lo haga>. <
msw

1
Jaja. Francis Bacon, uno de los mejores ensayistas de Gran Bretaña, de quien algunas personas sospechan que escribió las obras de Shakespeare, porque no pueden aceptar que un niño de escuela primaria del país, hijo de un glover, pueda ser un genio. Tal es el sistema de clases de inglés. Jesús dijo: "Yo soy la verdad". oregonstate.edu/instruct/phl302/texts/bacon/bacon_essays.html
Rob Kent

84

Una pequeña adición a todas las respuestas:

si haces algo así:

#include<stdio.h>
#include <stdlib.h>
int * foo(){
    int a = 5;
    return &a;
}
void boo(){
    int a = 7;

}
int main(){
    int * p = foo();
    boo();
    printf("%d\n",*p);
}

la salida probablemente será: 7

Esto se debe a que después de regresar de foo (), la pila se libera y luego se reutiliza por boo (). Si desmonta el ejecutable, lo verá claramente.


2
Ejemplo simple pero excelente para comprender la teoría subyacente de la pila. Solo una adición de prueba, declarando "int a = 5;" en foo () como "static int a = 5;" se puede usar para comprender el alcance y el tiempo de vida de una variable estática.
control

15
-1 "para probablemente sea 7 ". El compilador puede registrar un in boo. Puede eliminarlo porque es innecesario. Existe una buena posibilidad de que * p no sea 5 , pero eso no significa que haya una razón particularmente buena por la que probablemente sea 7 .
Matt

2
Se llama comportamiento indefinido!
Francis Cugler

¿Por qué y cómo booreutiliza la foopila? no son pilas de funciones separadas entre sí, también recibo basura al ejecutar este código en Visual Studio 2015
ampawd

1
@ampawd tiene casi un año, pero no, las "pilas de funciones" no están separadas entre sí. Un contexto tiene una pila. Ese contexto utiliza su pila para ingresar a main, luego desciende a foo(), existe, luego desciende a boo(). Foo()y Boo()ambos ingresan con el puntero de la pila en la misma ubicación. Sin embargo, este no es un comportamiento en el que se deba confiar. Otro 'cosas' (como interrupciones, o el sistema operativo) pueden utilizar la pila entre la llamada de boo()y foo(), modificando su contenido ...
Russ Schultz

72

En C ++, puede acceder a cualquier dirección, pero eso no significa que deba hacerlo . La dirección a la que accede ya no es válida. Se trabaja porque nada más revueltos de la memoria después de foo regresó, pero podría estrellarse en muchas circunstancias. Intente analizar su programa con Valgrind , o incluso compilarlo optimizado, y vea ...


55
Probablemente quiere decir que puede intentar acceder a cualquier dirección. Porque la mayoría de los sistemas operativos actuales no permitirán que ningún programa acceda a ninguna dirección; Hay toneladas de salvaguardas para proteger el espacio de direcciones. Es por eso que no habrá otro LOADLIN.EXE por ahí.
v010dya

67

Nunca lanza una excepción de C ++ accediendo a memoria no válida. Solo está dando un ejemplo de la idea general de hacer referencia a una ubicación de memoria arbitraria. Podría hacer lo mismo así:

unsigned int q = 123456;

*(double*)(q) = 1.2;

Aquí simplemente estoy tratando 123456 como la dirección de un doble y le escribo. Pueden suceder muchas cosas:

  1. qde hecho, podría ser una dirección válida de un doble, por ejemplo double p; q = &p;.
  2. q podría apuntar a algún lugar dentro de la memoria asignada y simplemente sobrescribo 8 bytes allí.
  3. q apunta fuera de la memoria asignada y el administrador de memoria del sistema operativo envía una señal de falla de segmentación a mi programa, lo que hace que el tiempo de ejecución lo finalice.
  4. Tú ganas la lotería.

La forma en que lo configura es un poco más razonable que la dirección devuelta apunte a un área válida de memoria, ya que probablemente estará un poco más abajo en la pila, pero sigue siendo una ubicación no válida a la que no puede acceder en un moda determinista

Nadie verificará automáticamente la validez semántica de las direcciones de memoria como esa durante la ejecución normal del programa. Sin embargo, un depurador de memoria como lo valgrindhará felizmente, por lo que debe ejecutar su programa a través de él y presenciar los errores.


99
Voy a escribir un programa ahora que sigue ejecutando este programa para que4) I win the lottery
Aidiakapi

29

¿Compiló su programa con el optimizador habilitado? La foo()función es bastante simple y podría haber sido incorporada o reemplazada en el código resultante.

Pero estoy de acuerdo con Mark B en que el comportamiento resultante no está definido.


Esa es mi apuesta Optimizer volcó la llamada a la función.
Erik Aronesty

99
Eso no es necesario. Como no se llama a una nueva función después de foo (), el marco de la pila local de funciones simplemente todavía no se sobrescribe. Agregue otra invocación de función después de foo (), y 5se cambiará ...
Tomas

Ejecuté el programa con GCC 4.8, reemplazando cout con printf (e incluyendo stdio). Advierte correctamente "advertencia: la dirección de la variable local 'a' devuelve [-Wreturn-local-addr]". Salidas 58 sin optimización y 08 con -O3. Extrañamente, P tiene una dirección, a pesar de que su valor es 0. Esperaba NULL (0) como dirección.
kevinf

23

Su problema no tiene nada que ver con el alcance . En el código que muestra, la función mainno ve los nombres en la función foo, por lo que no puede acceder aen foo directamente con este nombre afuera foo.

El problema que tiene es por qué el programa no señala un error al hacer referencia a memoria ilegal. Esto se debe a que los estándares C ++ no especifican un límite muy claro entre la memoria ilegal y la memoria legal. Hacer referencia a algo en la pila emergente a veces causa error y otras no. Depende. No cuentes con este comportamiento. Suponga que siempre generará un error cuando programe, pero suponga que nunca indicará un error cuando depure.


Recuerdo de una copia antigua de Turbo C Programming para IBM , con la que jugaba cuando, cómo se describía con gran detalle la manipulación directa de la memoria gráfica y el diseño de la memoria de video en modo texto de IBM. Por supuesto, entonces, el sistema en el que se ejecutaba el código definía claramente lo que significaba escribir en esas direcciones, por lo que siempre que no se preocupara por la portabilidad a otros sistemas, todo estaba bien. IIRC, los punteros al vacío eran un tema común en ese libro.
un CVn

@ Michael Kjörling: ¡Claro! A la gente le gusta hacer algún trabajo sucio de vez en cuando;)
Chang Peng

18

Solo está devolviendo una dirección de memoria, está permitido pero probablemente sea un error.

Sí, si intenta desreferenciar esa dirección de memoria, tendrá un comportamiento indefinido.

int * ref () {

 int tmp = 100;
 return &tmp;
}

int main () {

 int * a = ref();
 //Up until this point there is defined results
 //You can even print the address returned
 // but yes probably a bug

 cout << *a << endl;//Undefined results
}

No estoy de acuerdo: hay un problema antes del cout. *aapunta a memoria no asignada (liberada). Incluso si no lo abandonas, sigue siendo peligroso (y probablemente falso).
antes

@ereOn: Aclaré más a qué me refería con problema, pero no, no es peligroso en términos de código válido de c ++. Pero es peligroso en términos de probabilidad de que el usuario haya cometido un error y haga algo malo. Tal vez, por ejemplo, está tratando de ver cómo crece la pila, y solo le importa el valor de la dirección y nunca lo desreferenciará.
Brian R. Bondy

18

Ese es un comportamiento indefinido clásico que se discutió aquí no hace dos días: busque un poco en el sitio. En pocas palabras, tuvo suerte, pero cualquier cosa podría haber sucedido y su código está haciendo un acceso no válido a la memoria.


18

Este comportamiento es indefinido, como señaló Alex; de hecho, la mayoría de los compiladores advertirán contra hacerlo, porque es una manera fácil de obtener bloqueos.

Para ver un ejemplo del tipo de comportamiento espeluznante que es probable que tenga, pruebe este ejemplo:

int *a()
{
   int x = 5;
   return &x;
}

void b( int *c )
{
   int y = 29;
   *c = 123;
   cout << "y=" << y << endl;
}

int main()
{
   b( a() );
   return 0;
}

Esto imprime "y = 123", pero sus resultados pueden variar (¡de verdad!). Su puntero está golpeando otras variables locales no relacionadas.


18

Presta atención a todas las advertencias. No solo resuelva errores.
GCC muestra esta advertencia

advertencia: se devuelve la dirección de la variable local 'a'

Este es el poder de C ++. Deberías preocuparte por la memoria. Con la -Werrorbandera, esta advertencia se convierte en un error y ahora debe depurarla.


17

Funciona porque la pila no ha sido alterada (todavía) desde que se colocó allí. Llame a algunas otras funciones (que también están llamando a otras funciones) antes de acceder anuevamente y probablemente ya no tenga tanta suerte ... ;-)


16

En realidad invocaste un comportamiento indefinido.

Devolver la dirección de un trabajo temporal, pero como los temporales se destruyen al final de una función, los resultados de acceder a ellos serán indefinidos.

Por lo tanto, no modificó asino la ubicación de la memoria donde aalguna vez estuvo. Esta diferencia es muy similar a la diferencia entre estrellarse y no estrellarse.


14

En las implementaciones típicas del compilador, puede pensar en el código como "imprimir el valor del bloque de memoria con la dirección que solía estar ocupada por un". Además, si agrega una nueva invocación de función a una función que mantiene un local int, es muy probable que cambie el valor de a(o la dirección de memoria que asolía señalar). Esto sucede porque la pila se sobrescribirá con un nuevo marco que contiene datos diferentes.

Sin embargo, este es un comportamiento indefinido y no debe confiar en que funcione.


3
"imprimir el valor del bloque de memoria con la dirección que solía estar ocupada por un" no es del todo correcto. Esto hace que parezca que su código tiene un significado bien definido, que no es el caso. Sin embargo, tiene razón en que esta es probablemente la forma en que la mayoría de los compiladores lo implementaría.
Brennan Vincent

@BrennanVincent: Mientras el almacenamiento estaba ocupado por a, el puntero contenía la dirección de a. Aunque el Estándar no requiere que las implementaciones definan el comportamiento de las direcciones después de que la vida útil de su destino haya finalizado, también reconoce que en algunas plataformas UB se procesa de manera documentada, característica del entorno. Si bien la dirección de una variable local generalmente no será de mucha utilidad después de que se haya salido del alcance, algunos otros tipos de direcciones aún pueden ser significativos después de la vida útil de sus objetivos respectivos.
supercat

@BrennanVincent: Por ejemplo, si bien el Estándar puede no requerir que las implementaciones permitan que un puntero pasado reallocse compare con el valor de retorno, ni que los punteros a direcciones dentro del bloque antiguo se ajusten para apuntar al nuevo, algunas implementaciones lo hacen , y el código que explota dicha característica puede ser más eficiente que el código que debe evitar cualquier acción, incluso comparaciones, que impliquen punteros a la asignación que se le asignó realloc.
supercat

14

Puede, porque aes una variable asignada temporalmente para la vida útil de su alcance ( foofunción). Después de que regrese de foola memoria es libre y se puede sobrescribir.

Lo que estás haciendo se describe como un comportamiento indefinido . El resultado no se puede predecir.


12

Las cosas con salida de consola correcta (?) Pueden cambiar dramáticamente si usa :: printf pero no cout. Puede jugar con el depurador dentro del código siguiente (probado en x86, 32 bits, MSVisual Studio):

char* foo() 
{
  char buf[10];
  ::strcpy(buf, "TEST”);
  return buf;
}

int main() 
{
  char* s = foo();    //place breakpoint & check 's' varialbe here
  ::printf("%s\n", s); 
}

5

Después de regresar de una función, todos los identificadores se destruyen en lugar de los valores guardados en una ubicación de memoria y no podemos ubicar los valores sin tener un identificador. Pero esa ubicación aún contiene el valor almacenado por la función anterior.

Entonces, aquí la función foo()está devolviendo la dirección dea y ase destruye después de devolver su dirección. Y puede acceder al valor modificado a través de esa dirección devuelta.

Déjame tomar un ejemplo del mundo real:

Supongamos que un hombre esconde dinero en una ubicación y le dice la ubicación. Después de un tiempo, el hombre que le había dicho la ubicación del dinero muere. Pero aún así tienes acceso a ese dinero escondido.


4

Es una forma "sucia" de usar direcciones de memoria. Cuando devuelve una dirección (puntero) no sabe si pertenece al ámbito local de una función. Es solo una dirección. Ahora que invocó la función 'foo', esa dirección (ubicación de memoria) de 'a' ya estaba asignada allí en la memoria direccionable (segura, por ahora al menos) de su aplicación (proceso). Después de que la función 'foo' regresó, la dirección de 'a' puede considerarse 'sucia' pero está allí, no limpiada, ni alterada / modificada por expresiones en otra parte del programa (al menos en este caso específico). El compilador de AC / C ++ no le impide tener un acceso tan 'sucio' (sin embargo, podría advertirle si le importa).


1

Tu código es muy arriesgado. Está creando una variable local (que se considera destruida después de que finaliza la función) y devuelve la dirección de memoria de esa variable después de que se destruye.

Eso significa que la dirección de memoria podría ser válida o no, y su código será vulnerable a posibles problemas de dirección de memoria (por ejemplo, falla de segmentación).

Esto significa que está haciendo algo muy malo, porque está pasando una dirección de memoria a un puntero que no es confiable en absoluto.

Considere este ejemplo, en cambio, y pruébelo:

int * foo()
{
   int *x = new int;
   *x = 5;
   return x;
}

int main()
{
    int* p = foo();
    std::cout << *p << "\n"; //better to put a new-line in the output, IMO
    *p = 8;
    std::cout << *p;
    delete p;
    return 0;
}

A diferencia de su ejemplo, con este ejemplo usted es:

  • asignación de memoria para int en una función local
  • esa dirección de memoria sigue siendo válida también cuando caduca la función (nadie la elimina)
  • la dirección de memoria es confiable (ese bloque de memoria no se considera libre, por lo que no se anulará hasta que se elimine)
  • la dirección de memoria debe eliminarse cuando no se usa. (ver la eliminación al final del programa)

¿Agregó algo que aún no está cubierto por las respuestas existentes? Y por favor no use punteros en bruto new.
Carreras de ligereza en órbita

1
El autor de la pregunta utilizó punteros en bruto. Hice un ejemplo que reflejaba exactamente el ejemplo que hizo para permitirle ver la diferencia entre un puntero no confiable y uno confiable. En realidad, hay otra respuesta similar a la mía, pero usa strcpy que, en mi humilde opinión, podría ser menos claro para un programador novato que mi ejemplo que usa nuevo.
Nobun

No lo usaron new. Les estás enseñando a usar new. Pero no deberías usarlo new.
Carreras de ligereza en órbita

Entonces, en su opinión, ¿es mejor pasar una dirección a una variable local que se destruye en una función que asignar memoria? Esto no tiene sentido. Es importante comprender el concepto de asignación y desasignación de memoria, en mi opinión, principalmente si está preguntando sobre punteros (el autor no utilizó punteros nuevos, sino usados).
Nobun

¿Cuando dije eso? No, es mejor utilizar punteros inteligentes para indicar adecuadamente la propiedad del recurso referenciado. ¡No lo use newen 2019 (a menos que esté escribiendo código de biblioteca) y tampoco enseñe a los recién llegados a hacerlo! Salud.
Carreras de ligereza en órbita
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.