Comment est-ce possible? La mémoire d'une variable locale n'est-elle pas inaccessible en dehors de sa fonction?
Vous louez une chambre d'hôtel. Vous mettez un livre dans le tiroir supérieur de la table de chevet et vous vous endormez. Vous vérifiez le lendemain matin, mais "oubliez" de rendre votre clé. Vous volez la clé!
Une semaine plus tard, vous rentrez à l'hôtel, ne vous enregistrez pas, vous faufilez dans votre ancienne chambre avec votre clé volée et regardez dans le tiroir. Votre livre est toujours là. Étonnant!
Comment est-ce possible? Le contenu d'un tiroir de chambre d'hôtel n'est-il pas inaccessible si vous n'avez pas loué la chambre?
Eh bien, évidemment, ce scénario peut se produire dans le monde réel sans problème. Aucune force mystérieuse ne fait disparaître votre livre lorsque vous n'êtes plus autorisé à être dans la pièce. Il n'y a pas non plus de force mystérieuse qui vous empêche d'entrer dans une pièce avec une clé volée.
La direction de l'hôtel n'est pas obligée de retirer votre livre. Vous n'avez pas passé de contrat avec eux qui stipule que si vous laissez des choses derrière vous, ils les déchireront pour vous. Si vous rentrez illégalement dans votre chambre avec une clé volée pour la récupérer, le personnel de sécurité de l'hôtel n'est pas obligé de vous attraper en vous faufilant. plus tard, vous devez m'arrêter. " Vous avez plutôt signé avec eux un contrat qui disait "Je promets de ne pas rentrer plus tard dans ma chambre", un contrat que vous avez rompu .
Dans cette situation tout peut arriver . Le livre peut être là - vous avez eu de la chance. Le livre de quelqu'un d'autre peut être là et le vôtre pourrait être dans le four de l'hôtel. Quelqu'un pourrait être là à votre arrivée, déchirant votre livre en morceaux. L'hôtel aurait pu retirer complètement la table et le livre et les remplacer par une armoire. Tout 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 vous faufilez.
Vous ne savez pas ce qui va se passer; lorsque vous avez quitté l'hôtel et volé une clé pour l'utiliser illégalement plus tard, vous avez renoncé au droit de vivre dans un monde prévisible et sûr parce que vous avez choisi d'enfreindre les règles du système.
C ++ n'est pas un langage sûr . Il vous permettra joyeusement d'enfreindre les règles du système. Si vous essayez de faire quelque chose d'illégal et de stupide, comme rentrer dans une pièce dans laquelle vous n'êtes pas autorisé à fouiller et fouiller dans un bureau qui pourrait même ne plus être là, C ++ ne vous arrêtera pas. Des langages plus sûrs que C ++ résolvent ce problème en limitant votre pouvoir - en ayant un contrôle beaucoup plus strict sur les clés, par exemple.
MISE À JOUR
Sainte bonté, cette réponse retient beaucoup l'attention. (Je ne sais pas pourquoi - j'ai considéré que c'était juste une petite analogie "amusante", mais peu importe.)
J'ai pensé qu'il pourrait être pertinent de mettre à jour cela un peu avec quelques réflexions plus techniques.
Les compilateurs sont chargés de générer du code qui gère le stockage des données manipulées par ce programme. Il existe de nombreuses façons différentes de générer du code pour gérer la mémoire, mais au fil du temps, deux techniques de base se sont enracinées.
La première consiste à avoir une sorte de zone de stockage "à longue durée de vie" où la "durée de vie" de chaque octet dans le stockage - c'est-à-dire la période de temps où il est valablement associé à une variable de programme - ne peut pas être facilement prédite à l'avance de temps. Le compilateur génère des appels dans un «gestionnaire de tas» qui sait allouer dynamiquement du stockage quand il est nécessaire et le récupérer lorsqu'il n'est plus nécessaire.
La deuxième méthode consiste à avoir une zone de stockage «à durée de vie courte» où la durée de vie de chaque octet est bien connue. Ici, les durées de vie suivent un schéma de «nidification». La plus longue durée de vie de ces variables de courte durée sera allouée avant toute autre variable de courte durée et sera libérée en dernier. Les variables à durée de vie plus courte seront allouées après les plus longues et seront libérées avant elles. La durée de vie de ces variables à durée de vie plus courte est «imbriquée» dans la durée de vie de celles à durée de vie plus longue.
Les variables locales suivent ce dernier modèle; lorsqu'une méthode est entrée, ses variables locales prennent vie. Lorsque cette méthode appelle une autre méthode, les variables locales de la nouvelle méthode prennent vie. Ils seront morts avant la mort des variables locales de la première méthode. L'ordre relatif des débuts et des fins de durée de vie des stockages associés aux variables locales peut être déterminé à l'avance.
Pour cette raison, les variables locales sont généralement générées en tant que stockage sur une structure de données «pile», car une pile a la propriété que la première chose poussée dessus sera la dernière chose sautée.
C'est comme si l'hôtel décide de ne louer les chambres que séquentiellement, et vous ne pouvez pas partir avant que tout le monde avec un numéro de chambre plus élevé que vous ne l'ait fait.
Pensons donc à la pile. Dans de nombreux systèmes d'exploitation, vous obtenez une pile par thread et la pile est allouée pour avoir une certaine taille fixe. Lorsque vous appelez une méthode, le contenu est poussé dans la pile. Si vous passez ensuite un pointeur vers la pile hors de votre méthode, comme le fait l'affiche originale ici, ce n'est qu'un pointeur au milieu d'un bloc de mémoire d'un million d'octets entièrement valide. Dans notre analogie, vous quittez l'hôtel; lorsque vous le faites, vous venez de vérifier la chambre occupée au numéro le plus élevé. Si personne d'autre ne s'enregistre après vous et que vous rentrez illégalement dans votre chambre, toutes vos affaires sont garanties d'être toujours là dans cet hôtel particulier .
Nous utilisons des piles pour les magasins temporaires car elles sont vraiment bon marché et faciles. Une implémentation de C ++ n'est pas requise pour utiliser une pile pour le stockage des sections locales; il pourrait utiliser le tas. Ce n'est pas le cas, car cela ralentirait le programme.
Une implémentation de C ++ n'est pas requise pour laisser intactes les déchets que vous avez laissés sur la pile afin que vous puissiez y revenir plus tard illégalement; il est parfaitement légal pour le compilateur de générer du code qui remet à zéro tout dans la "salle" que vous venez de quitter. Ce n'est pas parce que, encore une fois, ce serait cher.
Une implémentation de C ++ n'est pas requise pour garantir que lorsque la pile diminue logiquement, les adresses qui étaient valides sont toujours mappées en mémoire. L'implémentation est autorisée à dire au système d'exploitation "nous avons fini d'utiliser cette page de pile maintenant. Jusqu'à ce que je dise le contraire, émettez une exception qui détruit le processus si quelqu'un touche la page de pile précédemment valide". Encore une fois, les implémentations ne le font pas réellement car elles sont lentes et inutiles.
Au lieu de cela, les implémentations vous permettent de faire des erreurs et de vous en tirer. La plupart du temps. Jusqu'au jour où quelque chose de vraiment horrible se passe mal et le processus explose.
C'est problématique. Il y a beaucoup de règles et il est très facile de les briser accidentellement. J'ai certainement plusieurs fois. Et pire, le problème n'apparaît souvent que lorsque la mémoire est détectée comme étant corrompue des milliards de nanosecondes après que la corruption s'est produite, lorsqu'il est très difficile de déterminer qui l'a gâchée.
Des langues plus sûres en mémoire résolvent ce problème en limitant votre puissance. En C # "normal", il n'y a tout simplement aucun moyen de prendre l'adresse d'un local et de la retourner ou de la stocker pour plus tard. Vous pouvez prendre l'adresse d'un local, mais la langue est intelligemment conçue de sorte qu'il est impossible de l'utiliser après la durée de vie des extrémités locales. Afin de prendre l'adresse d'un local et de la transmettre, vous devez mettre le compilateur dans un mode spécial "dangereux", et mettre le mot "dangereux" dans votre programme, pour attirer l'attention sur le fait que vous faites probablement quelque chose de dangereux qui pourrait enfreindre les règles.
Pour en savoir plus:
address of local variable ‘a’ returned
; valgrind spectaclesInvalid write of size 4 [...] Address 0xbefd7114 is just below the stack ptr