Como pode ser? A memória de uma variável local não está inacessível fora de sua função?
Você aluga um quarto de hotel. Você coloca um livro na gaveta superior da mesa de cabeceira e vai dormir. Você faz check-out na manhã seguinte, mas "esquece" de devolver sua chave. Você rouba a chave!
Uma semana depois, você volta ao hotel, não faz o check-in, entra no seu antigo quarto com a chave roubada e olha na gaveta. Seu livro ainda está lá. Surpreendente!
Como pode ser? O conteúdo de uma gaveta de quarto de hotel não está inacessível se você não alugou o quarto?
Bem, obviamente esse cenário pode acontecer no mundo real sem problemas. Não existe uma força misteriosa que faz com que seu livro desapareça quando você não está mais autorizado a estar na sala. Também não há uma força misteriosa que o impeça de entrar em uma sala com uma chave roubada.
A gerência do hotel não é obrigada a remover seu livro. Você não fez um contrato com eles, dizendo que, se você deixar as coisas para trás, elas irão rasgar para você. Se você entrar ilegalmente no seu quarto com uma chave roubada para recuperá-lo, a equipe de segurança do hotel não será obrigada a pegá-lo se escondendo. Você não fez um contrato com eles que dizia "se eu tentar entrar furtivamente no meu quarto" quarto mais tarde, você é obrigado a me parar. " Em vez disso, você assinou um contrato com eles que dizia "Prometo não voltar mais tarde para o meu quarto", um contrato que você quebrou .
Nesta situação, tudo pode acontecer . O livro pode estar lá - você teve sorte. O livro de outra pessoa pode estar lá e o seu pode estar no forno do hotel. Alguém pode estar lá quando você entra, rasgando seu livro em pedaços. O hotel poderia ter removido completamente a mesa e o livro e substituído por um guarda-roupa. Todo o hotel pode estar prestes a ser demolido e substituído por um estádio de futebol, e você morrerá em uma explosão enquanto estiver se esgueirando.
Você não sabe o que vai acontecer; quando você check-out do hotel e roubou uma chave para ilegalmente usar mais tarde, você deu-se o direito de viver em um mundo seguro previsível porque você escolheu para quebrar as regras do sistema.
C ++ não é uma linguagem segura . Ele alegremente permitirá que você quebre as regras do sistema. Se você tentar fazer algo ilegal e tolo, como voltar para uma sala em que não está autorizado a entrar e vasculhar uma mesa que talvez nem esteja mais lá, o C ++ não o impedirá. Linguagens mais seguras que o C ++ resolvem esse problema restringindo seu poder - tendo um controle muito mais rigoroso sobre as chaves, por exemplo.
ATUALIZAR
Santo Deus, esta resposta está recebendo muita atenção. (Não sei por que - considerei apenas uma pequena analogia "divertida", mas tanto faz.)
Eu pensei que poderia ser pertinente atualizar isso um pouco com mais alguns pensamentos técnicos.
Compiladores estão no negócio de gerar código que gerencia o armazenamento dos dados manipulados por esse programa. Existem várias maneiras diferentes de gerar código para gerenciar a memória, mas com o tempo duas técnicas básicas foram entrincheiradas.
O primeiro é ter algum tipo de área de armazenamento "de longa duração" em que a "vida útil" de cada byte no armazenamento - ou seja, o período em que ele é validamente associado a alguma variável de programa - não possa ser facilmente prevista com antecedência de tempo. O compilador gera chamadas para um "gerenciador de heap" que sabe como alocar dinamicamente o armazenamento quando necessário e recuperá-lo quando não for mais necessário.
O segundo método é ter uma área de armazenamento de "curta duração", onde a vida útil de cada byte é bem conhecida. Aqui, as vidas seguem um padrão de "aninhamento". A vida útil mais longa dessas variáveis de vida curta será alocada antes de qualquer outra variável de vida curta e será liberada por último. Variáveis de vida mais curta serão alocadas após as de vida mais longa e serão liberadas antes delas. O tempo de vida dessas variáveis de vida mais curta é "aninhado" dentro da vida das variáveis de vida mais longa.
Variáveis locais seguem o último padrão; Quando um método é inserido, suas variáveis locais ganham vida. Quando esse método chama outro método, as variáveis locais do novo método ganham vida. Eles estarão mortos antes que as variáveis locais do primeiro método estejam mortas. A ordem relativa do início e do fim da vida útil dos armazenamentos associados às variáveis locais pode ser calculada com antecedência.
Por esse motivo, as variáveis locais geralmente são geradas como armazenamento em uma estrutura de dados de "pilha", porque uma pilha tem a propriedade que a primeira coisa que for pressionada será a última que foi retirada.
É como se o hotel decidisse alugar apenas os quartos sequencialmente, e você não poderá fazer check-out até que todos com um número de quarto maior do que o seu.
Então, vamos pensar sobre a pilha. Em muitos sistemas operacionais, você obtém uma pilha por encadeamento e a pilha é alocada para ter um determinado tamanho fixo. Quando você chama um método, as coisas são colocadas na pilha. Se você passar um ponteiro para a pilha de volta do seu método, como o pôster original faz aqui, isso é apenas um ponteiro para o meio de algum bloco de memória de milhões de bytes totalmente válido. Em nossa analogia, você faz check-out do hotel; quando fizer isso, você acabou de sair da sala ocupada com o número mais alto. Se ninguém mais fizer o check-in depois de você e você voltar ilegalmente ao seu quarto, todas as suas coisas ainda estarão garantidas nesse hotel em particular .
Usamos pilhas para lojas temporárias porque são realmente baratas e fáceis. Uma implementação do C ++ não é necessária para usar uma pilha para armazenamento de locais; poderia usar a pilha. Não, porque isso tornaria o programa mais lento.
Uma implementação do C ++ não é necessária para deixar o lixo que você deixou na pilha intocado, para que você possa retornar posteriormente ilegalmente; é perfeitamente legal para o compilador gerar código que volta a zero tudo na "sala" que você acabou de desocupar. Não é porque, novamente, isso seria caro.
Uma implementação do C ++ não é necessária para garantir que, quando a pilha diminui logicamente, os endereços que costumavam ser válidos ainda sejam mapeados na memória. A implementação está autorizada a dizer ao sistema operacional "terminamos de usar esta página da pilha agora. Até que eu diga o contrário, emita uma exceção que destrói o processo se alguém tocar na página da pilha anteriormente válida". Novamente, as implementações não fazem isso porque são lentas e desnecessárias.
Em vez disso, as implementações permitem que você cometa erros e se livre dele. A maior parte do tempo. Até que um dia algo realmente terrível dê errado e o processo exploda.
Isso é problemático. Existem muitas regras e é muito fácil quebrá-las acidentalmente. Eu certamente tenho muitas vezes. E pior, o problema geralmente surge quando a memória é detectada como bilhões de nanossegundos corrompidos após a corrupção, quando é muito difícil descobrir quem estragou tudo.
Idiomas mais seguros para a memória resolvem esse problema restringindo seu poder. No C # "normal", simplesmente não há como pegar o endereço de um local e devolvê-lo ou armazená-lo para mais tarde. Você pode usar o endereço de um local, mas o idioma foi projetado de maneira inteligente, para que seja impossível usá-lo após o término da vida útil do local. Para pegar o endereço de um local e devolvê-lo, é necessário colocar o compilador em um modo "não seguro" especial e colocar a palavra "não seguro" em seu programa, para chamar a atenção para o fato de que você provavelmente está fazendo algo perigoso que poderia estar violando as regras.
Para leitura adicional:
address of local variable ‘a’ returned
; valgrind showsInvalid write of size 4 [...] Address 0xbefd7114 is just below the stack ptr