
En programmation, les pointeurs orphelins et les pointeurs non valides sont des pointeurs qui ne pointent pas vers un objet valide du type approprié. Il s'agit de cas particuliers de violations de la sécurité mémoire . Plus généralement, les références orphelines et les références non valides sont des références qui ne mènent pas à une destination valide.
Les pointeurs non valides apparaissent lors de la destruction d'un objet , lorsqu'un objet pointé par un pointeur donné est supprimé ou désalloué sans que la valeur dudit pointeur soit modifiée. Le pointeur pointe alors toujours vers l'emplacement mémoire désalloué. Le système peut réallouer la mémoire précédemment libérée, et si le programme déréférence ensuite le pointeur (désormais) non valide, un comportement imprévisible peut se produire , car la mémoire peut maintenant contenir des données complètement différentes. Si le programme écrit dans la mémoire référencée par un pointeur non valide, une corruption silencieuse de données sans rapport peut survenir, entraînant des bogues subtils extrêmement difficiles à détecter. Si la mémoire a été réallouée à un autre processus, toute tentative de déréférencement du pointeur non valide peut provoquer des erreurs de segmentation (UNIX, Linux) ou des erreurs de protection générale (Windows). Si le programme dispose des privilèges suffisants pour écraser les données de gestion de la mémoire utilisées par l'allocateur du noyau, la corruption peut engendrer des instabilités du système. Dans les langages orientés objet avec ramasse-miettes , les références orphelines sont évitées en ne détruisant que les objets inaccessibles, c'est-à-dire ceux qui ne possèdent aucun pointeur entrant ; ceci est garanti par le traçage ou le comptage des références . Cependant, un finaliseur peut créer de nouvelles références à un objet, ce qui nécessite la résurrection de l'objet pour éviter une référence orpheline.
Les pointeurs sauvages, également appelés pointeurs non initialisés, apparaissent lorsqu'un pointeur est utilisé avant son initialisation à un état connu, ce qui est possible dans certains langages de programmation. Ils présentent le même comportement erratique que les pointeurs orphelins, mais ils ont moins de chances de passer inaperçus car de nombreux compilateurs émettent un avertissement à la compilation si des variables déclarées sont accédées avant leur initialisation.
langage C ), la suppression explicite d'un objet de la mémoire ou la destruction de son cadre de pile lors du retour d'une instruction ne modifie pas les pointeurs associés. Le pointeur continue de pointer vers le même emplacement mémoire, même si cet emplacement peut désormais être utilisé à d'autres fins.Un exemple simple est présenté ci-dessous :
Une autre cause fréquente de pointeurs non initialisés est une combinaison confuse d' appels de fonctions malloc()et free()de bibliothèques : un pointeur devient non initialisé lorsque le bloc de mémoire qu'il pointe est libéré. Comme dans l'exemple précédent, une façon d'éviter ce problème est de s'assurer de réinitialiser le pointeur à null après avoir libéré sa référence, comme illustré ci-dessous.
Désaffectation manuelle sans référence suspendue
Cause des pointeurs sauvages
Les pointeurs non initialisés sont créés en omettant l'initialisation nécessaire avant leur première utilisation. Ainsi, à proprement parler, tout pointeur dans les langages de programmation qui n'imposent pas d'initialisation est initialement un pointeur non initialisé.
Cela se produit le plus souvent lorsqu'on saute l'initialisation, et non lorsqu'on l'omet. La plupart des compilateurs sont capables de signaler ce problème.
Éviter les erreurs de pointeur orphelin
En C, la technique la plus simple consiste à implémenter une version alternative de la free()fonction (ou d'une fonction similaire) qui garantit la réinitialisation du pointeur. Cependant, cette technique ne supprime pas les autres variables de pointeur qui pourraient contenir une copie du pointeur.
Une autre approche consiste à utiliser le ramasse-miettes Boehm , un ramasse-miettes conservateur qui remplace les fonctions d'allocation mémoire standard en C et C++ par un ramasse-miettes. Cette approche élimine complètement les erreurs de pointeurs non initialisés en désactivant les libérations de mémoire et en récupérant les objets par le biais du ramasse-miettes.
Une autre approche consiste à utiliser un système tel que CHERI , qui stocke les pointeurs avec des métadonnées supplémentaires susceptibles d'empêcher les accès invalides en incluant des informations sur leur durée de vie. CHERI nécessite généralement une prise en charge par le processeur pour effectuer ces vérifications supplémentaires.
Dans les langages comme Java, les pointeurs non initialisés sont impossibles car il n'existe aucun mécanisme pour libérer explicitement la mémoire. C'est le ramasse-miettes qui peut libérer la mémoire, mais seulement lorsque l'objet n'est plus accessible par aucune référence.
En Rust , le système de types a été étendu pour inclure la durée de vie des variables et l'initialisation des ressources . Sauf si l'on désactive ces fonctionnalités, les pointeurs non initialisés sont détectés à la compilation et signalés comme des erreurs de programmation.
Détection de pointeur orphelin
Pour détecter les erreurs de pointeurs invalides, une technique courante consiste à affecter aux pointeurs une valeur nulle ou une adresse invalide une fois la mémoire pointée libérée. Lorsque le pointeur nul est déréférencé (dans la plupart des langages), le programme s'arrête immédiatement, éliminant ainsi tout risque de corruption de données ou de comportement imprévisible. L'erreur de programmation sous-jacente est alors plus facile à identifier et à corriger. Cette technique est cependant inefficace en présence de plusieurs copies du pointeur.
Certains débogueurs écrasent et détruisent automatiquement les données libérées, généralement selon un motif spécifique 0xDEADBEEF(par exemple, le débogueur Visual C/C++ de Microsoft utilise `libre` ou `libre` 0xCC, selon les données libérées ). Ceci empêche généralement la réutilisation des données en les rendant inutilisables et en les signalant clairement (le motif indique au programmeur que la mémoire a déjà été libérée).0xCD0xDD
Des outils tels que Polyspace , TotalView , Valgrind , Mudflap, AddressSanitizer ou des outils basés sur LLVM peuvent également être utilisés pour détecter les utilisations de pointeurs orphelins.
D'autres outils ( SoftBound , Insure++ et CheckPointer ) instrumentent le code source pour collecter et suivre les valeurs légitimes des pointeurs (« métadonnées ») et vérifier la validité de chaque accès au pointeur par rapport aux métadonnées.
Une autre stratégie, lorsqu'on soupçonne un petit ensemble de classes, consiste à rendre temporairement toutes leurs fonctions membres virtuelles : une fois l'instance de classe détruite/libérée, son pointeur vers la table des méthodes virtuelles est défini sur ` NULLtrue`, et tout appel à une fonction membre provoquera le plantage du programme et affichera le code coupable dans le débogueur.
L' extension de marquage de mémoire ARM64 (MTE) - désactivée par défaut sur les systèmes Linux, mais activable sur Android 16 - déclenche une erreur de segmentation lorsqu'elle détecte une utilisation après libération et un dépassement de tampon .