1138 votes

Une variable de la mémoire accessible à l'extérieur de son champ d'application?

J'ai le code suivant.

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

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

Et le code est en cours d'exécution avec aucune des exceptions d'exécution!

La sortie a été 5 8

Comment peut-il être? N'est-ce pas la mémoire d'une variable locale inaccessibles en dehors de sa fonction?

4964voto

Eric Lippert Points 300275

Comment peut-il être? N'est-ce pas la mémoire d'une variable locale inaccessibles en dehors de sa fonction?

Vous louez une chambre d'hôtel. Vous mettez un livre dans le premier tiroir de la table de chevet et d'aller dormir. Vous consultez le lendemain matin, mais "oublie" de donner en retour votre clé. Vous voler la clé!

Une semaine plus tard, vous retournez à l'hôtel, ne cochez pas la case, se faufiler dans votre ancienne chambre avec votre vol de clés, et de regarder dans le tiroir. Votre livre est toujours là. Étonnante!

Comment peut-il être? Ne sont pas le contenu d'une chambre d'hôtel tiroir inaccessible si vous n'avez pas loué la chambre?

Bon, évidemment, ce scénario peut se produire dans le monde réel, pas de problème. Il n'y a pas de force mystérieuse qui provoque votre livre à disparaître lorsque vous n'êtes plus autorisé à être présent dans la salle. Ni est-il une force mystérieuse qui vous empêche d'entrer dans une salle avec un vol de clé.

La direction de l'hôtel n'est pas nécessaire de supprimer votre livre. Vous n'avez pas de contrat avec eux, qui disait que si vous laissez des trucs derrière, ils vont déchiqueter pour vous. Si vous illégalement re-entrer dans votre chambre grâce à un vol de clé pour le récupérer, l'hôtel le personnel de sécurité n'est pas nécessaire de vous attraper se faufiler dans. Vous n'avez pas de contrat avec eux qui a dit "si j'essaie de se faufiler dans ma chambre plus tard, vous êtes tenu de m'arrêter." Plutôt, vous avez signé un contrat avec eux qui a dit "je promets de ne pas se faufiler dans ma chambre plus tard", un contrat qui vous a brisé.

Dans cette situation , tout peut arriver. Le livre peut être de là, vous avez eu de la chance. Quelqu'un d'autre livre peut être là et le vôtre pourrait être dans l'hôtel de la fournaise. Quelqu'un pourrait être là quand vous venez dans les, de déchirer votre livre de pièces. L'hôtel a supprimé la table et livre entièrement et l'a remplacée par une armoire. L'ensemble de l'hôtel pourrait être sur le point d'être démoli et remplacé par un stade de football, et vous allez mourir dans une explosion pendant que vous faufiler.

Vous ne savez pas ce qui va arriver, lors de la vérification de l'hôtel et a volé une clé à utiliser illégalement plus tard, vous a donné le droit de vivre dans un prévisibles, coffre-fort monde parce que vous avez choisi d'enfreindre les règles du système.

C++ n'est pas un langage sûr. Il gaiement, vous permettent de briser les règles du système. Si vous essayez de faire quelque chose d'illégal et stupide comme revenir dans une chambre, vous n'êtes pas autorisé à être dans et en fouillant dans un bureau qui pourrait même ne pas être plus là, C++ ne va pas arrêter de vous. Plus sûr langues que le C++ résoudre ce problème en limitant votre alimentation -- en ayant beaucoup plus strictes de contrôle des clés, par exemple.

Mise à JOUR

Saint bonté, cette réponse est d'obtenir beaucoup d'attention. (Je ne suis pas sûr pourquoi, je considère que c'est juste un "plaisir" petite analogie, mais peu importe.)

J'ai pensé qu'il pourrait être pertinent de mettre à jour un peu avec un peu plus de la pensée technique.

Les compilateurs sont dans l'entreprise de générer du code qui gère le stockage des données manipulées par le programme. Il ya beaucoup de différentes façons de générer du code pour gérer la mémoire, mais au fil du temps deux techniques de base ont marqué.

La première est d'avoir une sorte de "longue durée" de la zone de stockage où la "durée de vie" de chaque octet dans le stockage, -- c'est la période de temps où il est régulièrement associé à certains variable de programme, ne peuvent pas facilement être prédit à l'avance. Le compilateur génère appelle un "gestionnaire de tas" qui sait comment allouer dynamiquement de la mémoire lorsque c'est nécessaire, et le récupérer quand il n'est plus nécessaire.

La seconde est d'avoir une sorte de "courte durée" la zone de stockage où la durée de vie de chaque octet dans le stockage est bien connu, et, en particulier, la durée de vie de stockages de suivre une "imbrication" de modèle. C'est, à l'affectation de la plus longue durée de vie de la courte durée de vie des variables strictement chevauche les affectations de courte durée de vie des variables qui viennent après lui.

Les variables locales suivre le dernier modèle; lorsqu'une méthode est entré, ses variables locales vivant. Lorsque cette méthode appelle une autre méthode, la nouvelle méthode de variables locales vivant. Ils seront morts avant la première méthode de variables locales sont morts. L'ordre relatif des le début et la fin de la durée de vie de stockages associés avec les variables locales peuvent être réglés à l'avance.

Pour cette raison, les variables locales sont généralement générés que le stockage sur une "pile" structure de données, en raison d'une pile a la propriété que la première chose a poussé sur que ça va être la dernière chose sauté.

C'est comme l'hôtel décide de ne louer des chambres de manière séquentielle, et vous ne pouvez pas vérifier jusqu'à ce que tout le monde avec un numéro plus élevé que vous a vérifié.

Nous allons donc réfléchir à la pile. Dans de nombreux systèmes d'exploitation, vous obtenez une pile par thread et la pile est allouée à une certaine taille fixe. Lorsque vous appelez une méthode, trucs est poussé sur la pile. Si vous puis de passer un pointeur sur la pile de retour de votre méthode, que l'original poster ici, c'est juste un pointeur vers le milieu de certains tout à fait valable millions d'octets du bloc de mémoire. Dans notre analogie, vous découvrez de l'hôtel; lorsque vous le faites, vous avez juste extrait de la plus haute numérotés à la chambre occupée. Si personne ne vérifie après vous, et vous retournez à votre chambre illégalement, tous vos trucs, est garanti d'être encore là, dans cet hôtel particulier.

Nous utilisons des piles pour les magasins temporaires parce qu'ils sont vraiment pas cher et facile. Une mise en œuvre de C++ n'est pas nécessaire d'utiliser une pile pour le stockage des habitants; il pourrait utiliser le tas. Ce n'est pas, parce que cela permettrait de rendre le programme plus lent.

Une mise en œuvre de C++ n'est pas nécessaire de laisser les ordures que vous avez laissé sur la pile intacte, de sorte que vous pouvez revenir plus tard illégalement; il est parfaitement légal pour le compilateur pour générer du code qui tourne le dos à zéro tout dans la "chambre" que vous venez de disparition. Ce n'est pas parce qu'encore une fois, ce serait cher.

Une mise en œuvre de C++ n'est pas nécessaire pour assurer que, lorsque la pile logiquement se rétrécit, les adresses utilisées pour être valides sont encore mappé en mémoire. La mise en œuvre est autorisé à dire le système d'exploitation "nous sommes fait à l'aide de cette page de pile maintenant. Jusqu'à ce que je dire autrement, la question d'une exception qui détruit le processus si quelqu'un touche le déjà-valide page de pile". De nouveau, les implémentations ne sont pas tout à fait parce qu'il est lent et inutile.

Au lieu de cela, les implémentations de vous permettre de faire des erreurs et sortir avec elle. La plupart du temps. Jusqu'au jour où quelque chose de vraiment terrible va mal et le processus explose.

C'est problématique. Il y a beaucoup de règles et il est très facile de les casser accidentellement. Je n'ai certainement de nombreuses fois. Et le pire, le problème est souvent que des surfaces lorsque la mémoire est détectée à être endommagé milliards de nanosecondes après la corruption qui s'est passé, quand il est très difficile de comprendre qui a tout fait foiré.

Plus de mémoire-safe langues de résoudre ce problème en limitant votre alimentation. "Normal", C# il y a tout simplement aucun moyen de prendre l'adresse d'un local et de le retourner ou de les conserver pour plus tard. Vous pouvez prendre l'adresse d'un local, mais la langue est habilement conçu de sorte qu'il est impossible de l'utiliser après la durée de vie de la locaux se termine. Afin de prendre l'adresse d'un local et de le transmettre à l'arrière, vous devez mettre le compilateur dans un spécial "dangereux" de mode, et de mettre le mot "dangereux" dans votre programme, afin d'appeler l'attention sur le fait que vous avez probablement fait quelque chose de dangereux qui pourraient briser les règles.

Pour davantage de lecture:

283voto

Rena Points 2240

Ce que vous faites ici est simplement de la lecture et de l'écriture à la mémoire utilisé pour être l'adresse d' a. Maintenant que vous êtes à l'extérieur de l' foo, c'est juste un pointeur vers un hasard zone de mémoire. Il se trouve que dans votre exemple, que la zone de mémoire n'existe pas et rien d'autre n'est à l'utiliser pour le moment. Vous ne cassez rien, en continuant à l'utiliser, et rien d'autre l'a remplacé. Par conséquent, l' 5 est toujours là. Dans un programme réel, que la mémoire sera ré-utilisé presque immédiatement et vous souhaitez casser quelque chose en faisant cela (bien que les symptômes peuvent ne pas apparaître jusqu'à ce que bien plus tard!)

Lorsque vous revenez d' foo, vous dites à l'OS que vous n'utilisez plus que de la mémoire et il peut être réaffecté à autre chose. Si vous avez de la chance et il ne fait jamais obtenir réaffectés, et le système ne permet pas l'attraper vous l'utiliser à nouveau, alors vous allez sortir avec le mensonge. Les Chances sont que vous finirez par écrit sur tout autre chose qui se termine avec cette adresse.

Maintenant, si vous vous demandez pourquoi le compilateur ne se plaint pas, c'est probablement parce qu' foo s'est fait éliminé par l'optimisation. Elle va vous avertir de ce genre de chose. C suppose que vous savez ce que vous faites bien, et techniquement, vous n'avez pas violé portée ici (il n'y a pas de référence à l' a hors d' foo), seul l'accès à la mémoire règles, qui déclenche un avertissement plutôt qu'une erreur.

En bref: ce ne sera pas pour habitude de travailler, mais parfois par hasard.

156voto

msw Points 25319

Car l'espace de stockage n'était pas piétiné sur l'instant. Ne comptez pas sur ce comportement.

71voto

Charles Brunet Points 5730

En C++, vous pouvez accéder à n'importe quelle adresse, mais il ne signifie pas que vous devriez. L'adresse que vous avez accès n'est plus valide. Il fonctionne parce que rien d'autre brouillé la mémoire après foo retourné, mais il risque de se bloquer dans de nombreuses circonstances. Essayez d'analyser votre programme avec Valgrind, ou même simplement de compiler optimisé, et voir...

52voto

Grav Points 854

C'est comme sauter la lumière rouge; vous pouvez obtenir de l'autre côté, ou vous pourriez avoir frappé par un camion!

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X