En C (ou C++ d'ailleurs), les pointeurs sont spéciaux s'ils ont la valeur zéro: on me conseille de mettre les pointeurs à zéro après avoir libéré leur mémoire, car cela signifie que libérer à nouveau le pointeur n'est pas dangereux; lorsque j'appelle malloc, il renvoie un pointeur avec la valeur zéro s'il ne peut pas me récupérer de mémoire; J'utilise if (p != 0)
tout le temps pour m'assurer que les pointeurs passés sont valides, etc.
Mais comme l'adressage de la mémoire commence à 0, 0 n'est-il pas une adresse aussi valide qu'une autre? Comment 0 peut-il être utilisé pour gérer des pointeurs nuls si c'est le cas? Pourquoi un nombre négatif n'est-il pas nul à la place?
Modifier:
Un tas de bonnes réponses. Je vais résumer ce qui a été dit dans les réponses exprimées lorsque mon propre esprit l'interprète et j'espère que la communauté me corrigera si je me méprends.
Comme tout le reste dans la programmation, c'est une abstraction. Juste une constante, pas vraiment liée à l'adresse 0. C++ 0x le souligne en ajoutant le mot clé nullptr
.
Ce n'est même pas une abstraction d'adresse, c'est la constante spécifiée par la norme C et le compilateur peut la traduire en un autre nombre tant qu'il s'assure qu'elle n'égale jamais une adresse "réelle", et égale d'autres pointeurs nuls si 0 n'est pas le meilleure valeur à utiliser pour la plate-forme.
Dans le cas où ce n'est pas une abstraction, ce qui était le cas au début, l'adresse 0 est utilisée par le système et interdite au programmeur.
Ma suggestion de nombre négatif était un petit brainstorming sauvage, je l'admets. L'utilisation d'un entier signé pour les adresses est un peu inutile si cela signifie qu'en dehors du pointeur nul (-1 ou autre), l'espace des valeurs est divisé également entre les entiers positifs qui font des adresses valides et les nombres négatifs qui sont juste gaspillés.
Si un nombre est toujours représentable par un type de données, c'est 0. (Probablement 1 aussi. Je pense à l'entier d'un bit qui serait 0 ou 1 s'il n'est pas signé, ou juste le bit signé s'il est signé, ou l'entier à deux bits qui serait [-2, 1]. Mais alors vous pouvez simplement choisir 0 étant nul et 1 étant le seul octet accessible en mémoire.)
Il y a encore quelque chose qui n'est pas résolu dans mon esprit. La question de débordement de pile pointeur vers une adresse fixe spécifique me dit que même si 0 pour le pointeur nul est une abstraction, un autre pointeur les valeurs ne le sont pas nécessairement. Cela m'amène à poster une autre question Stack Overflow, Pourrais-je jamais vouloir accéder à l'adresse zéro?.
2 points:
seule la valeur constante 0 dans le code source est le pointeur nul - l'implémentation du compilateur peut utiliser la valeur souhaitée ou nécessaire dans le code en cours d'exécution. Certaines plates-formes ont une valeur de pointeur spéciale non valide que l'implémentation peut utiliser comme pointeur nul. Le C FAQ a une question, "Sérieusement, est-ce que des machines réelles ont vraiment utilisé des pointeurs nuls différents de zéro ou des représentations différentes pour les pointeurs vers différents types?" , qui souligne plusieurs plates-formes qui utilisaient cette propriété de 0 comme pointeur nul dans la source C tout en étant représentées différemment à l'exécution. La norme C++ a une note qui indique clairement que la conversion "d'une expression constante intégrale avec la valeur zéro donne toujours un pointeur nul, mais la conversion d'autres expressions qui ont la valeur zéro n'ont pas besoin de produire un pointeur nul ".
une valeur négative pourrait être tout aussi utilisable par la plate-forme qu'une adresse - la norme C devait simplement choisir quelque chose à utiliser pour indiquer un pointeur nul, et zéro a été choisi. Honnêtement, je ne sais pas si d'autres valeurs sentinelles ont été prises en compte.
Les seules exigences pour un pointeur nul sont:
Historiquement, l'espace d'adressage commençant à 0 était toujours ROM, utilisé pour certains systèmes d'exploitation ou routines de gestion des interruptions de bas niveau, de nos jours, puisque tout est virtuel (y compris l'espace d'adressage), le système d'exploitation peut mapper n'importe quelle allocation à n'importe quelle adresse, de sorte qu'il peut spécifiquement PAS allouer quoi que ce soit à l'adresse 0.
IIRC, la valeur du "pointeur nul" n'est pas garantie d'être nulle. Le compilateur traduit 0 en n'importe quelle valeur "nulle" appropriée pour le système (qui dans la pratique est probablement toujours zéro, mais pas nécessairement). La même traduction est appliquée chaque fois que vous comparez un pointeur à zéro. Parce que vous ne pouvez comparer les pointeurs que les uns contre les autres et contre cette valeur spéciale-0, cela empêche le programmeur de connaître quoi que ce soit sur la représentation en mémoire du système. Quant à la raison pour laquelle ils ont choisi 0 au lieu de 42 ou un peu, je suppose que c'est parce que la plupart des programmeurs commencent à compter à 0 :) (De plus, sur la plupart des systèmes, 0 est la première adresse mémoire et ils voulaient que ce soit pratique, car dans pratiquer des traductions comme celles que je décris ont rarement lieu, le langage les autorise).
Vous devez mal comprendre la signification du zéro constant dans le contexte du pointeur.
Ni en C ni en C++, les pointeurs ne peuvent "avoir la valeur zéro". Les pointeurs ne sont pas des objets arithmétiques. Ils ne peuvent pas avoir de valeurs numériques comme "zéro" ou "négatif" ou quoi que ce soit de cette nature. Donc, votre déclaration sur "les pointeurs ... ont la valeur zéro" n'a tout simplement aucun sens.
En C et C++, les pointeurs peuvent avoir la réserve valeur de pointeur nul. La représentation réelle de la valeur du pointeur nul n'a rien à voir avec des "zéros". Cela peut être tout ce qui convient à une plate-forme donnée. Il est vrai que sur la plupart des plateformes, la valeur du pointeur nul est représentée physiquement par une valeur d'adresse zéro réelle. Cependant, si sur une plate-forme, l'adresse 0 est réellement utilisée dans un but (c'est-à-dire que vous pourriez avoir besoin de créer des objets à l'adresse 0), la valeur du pointeur nul sur cette plate-forme sera très probablement différente. Il pourrait être physiquement représenté par 0xFFFFFFFF
valeur d'adresse ou comme 0xBAADBAAD
valeur d'adresse, par exemple.
Néanmoins, quelle que soit la façon dont la valeur du pointeur nul est représentée sur une plate-forme donnée, dans votre code, vous continuerez à désigner des pointeurs nuls par une constante 0
. Afin d'affecter une valeur de pointeur nul à un pointeur donné, vous continuerez à utiliser des expressions comme p = 0
. Il est de la responsabilité du compilateur de réaliser ce que vous voulez et de le traduire dans la bonne représentation de la valeur du pointeur nul, c'est-à-dire de le traduire dans le code qui mettra la valeur d'adresse de 0xFFFFFFFF
dans le pointeur p
, par exemple.
En bref, le fait que vous utilisez 0
dans votre code Sorce pour générer des valeurs de pointeur nul ne signifie pas que la valeur de pointeur nul est en quelque sorte liée à l'adresse 0
. Le 0
que vous utilisez dans votre code source n'est qu'un "sucre syntaxique" qui n'a absolument aucun rapport avec l'adresse physique réelle vers laquelle la valeur du pointeur nul "pointe".
Mais comme l'adressage de la mémoire commence à 0, 0 n'est-il pas une adresse aussi valide qu'une autre?
Sur certains/plusieurs/tous les systèmes d'exploitation, l'adresse mémoire 0 est spéciale d'une manière ou d'une autre. Par exemple, il est souvent mappé sur une mémoire invalide/inexistante, ce qui provoque une exception si vous essayez d'y accéder.
Pourquoi un nombre négatif n'est-il pas nul à la place?
Je pense que les valeurs du pointeur sont généralement traitées comme des nombres non signés: sinon, par exemple, un pointeur 32 bits ne pourrait adresser que 2 Go de mémoire, au lieu de 4 Go.
Je suppose que la valeur magique 0 a été choisie pour définir un pointeur invalide car elle pourrait être testée avec moins d'instructions. Certains langages machine définissent automatiquement le zéro et signent les drapeaux en fonction des données lors du chargement des registres afin que vous puissiez tester un pointeur nul avec un simple chargement puis et des instructions de branchement sans faire une instruction de comparaison distincte.
(La plupart des ISA ne définissent que des indicateurs sur les instructions ALU, pas les charges, cependant. Et généralement, vous ne produisez pas de pointeurs via le calcul, sauf dans le compilateur lors de l'analyse de C source. Mais au moins, vous n'avez pas besoin une constante de largeur de pointeur arbitraire à comparer.)
Sur le Commodore Pet, Vic20 et C64 qui ont été les premières machines sur lesquelles j'ai travaillé, RAM a commencé à l'emplacement 0, il était donc tout à fait valide de lire et d'écrire à l'aide d'un pointeur nul si vous vouliez vraiment .
Je pense que c'est juste une convention. Il doit y avoir une valeur pour marquer un pointeur non valide.
Vous perdez juste un octet d'espace d'adressage, ce qui devrait rarement être un problème.
Il n'y a pas de pointeurs négatifs. Les pointeurs sont toujours non signés. De plus, s'ils pouvaient être négatifs, votre convention signifierait que vous perdriez la moitié de l'espace d'adressage.
Bien que C utilise 0 pour représenter le pointeur nul, gardez à l'esprit que la valeur du pointeur lui-même peut ne pas être un zéro. Cependant, la plupart des programmeurs n'utiliseront que des systèmes où le pointeur nul est, en fait, 0.
Mais pourquoi zéro? Eh bien, c'est une adresse que chaque système partage. Et souvent, les adresses basses sont réservées à des fins de système d'exploitation, donc la valeur fonctionne bien et est hors limites pour les programmes d'application. L'affectation accidentelle d'une valeur entière à un pointeur est aussi susceptible de finir à zéro que toute autre chose.
En ce qui concerne l'argument de ne pas définir un pointeur sur null après l'avoir supprimé afin que les suppressions futures "exposent les erreurs" ...
Si vous êtes vraiment, vraiment inquiet à ce sujet, alors une meilleure approche, qui est garantie de fonctionner, consiste à utiliser assert ():
...
assert(ptr && "You're deleting this pointer twice, look for a bug?");
delete ptr;
ptr = 0;
...
Cela nécessite une frappe supplémentaire et une vérification supplémentaire lors des builds de débogage, mais il est certain de vous donner ce que vous voulez: notez quand ptr est supprimé "deux fois". L'alternative donnée dans la discussion sur les commentaires, qui ne définit pas le pointeur sur null pour que vous obteniez un plantage, n'est tout simplement pas garantie de réussir. Pire, contrairement à ce qui précède, cela peut provoquer un crash (ou pire!) Sur un utilisateur si l'un de ces "bugs" parvient à l'étagère. Enfin, cette version vous permet de continuer à exécuter le programme pour voir ce qui se passe réellement.
Je me rends compte que cela ne répond pas à la question posée, mais je craignais que quelqu'un lisant les commentaires ne parvienne à la conclusion qu'il est considéré comme une "bonne pratique" de NE PAS mettre les pointeurs à 0 s'il est possible qu'ils soient envoyés à free () ou supprimer deux fois. Dans ces quelques cas où il est possible, il n'est JAMAIS une bonne pratique d'utiliser le comportement non défini comme outil de débogage. Personne qui n'a jamais eu à traquer un bogue qui a finalement été causé par la suppression d'un pointeur invalide ne le proposerait. Ces types d'erreurs prennent des heures à traquer et affectent presque toujours le programme d'une manière totalement inattendue qu'il est difficile, voire impossible, de retrouver le problème d'origine.
Historiquement, la faible mémoire d'une application était occupée par les ressources système. C'est à cette époque que zéro est devenu la valeur nulle par défaut.
Bien que ce ne soit pas nécessairement vrai pour les systèmes modernes, ce n'est toujours pas une bonne idée de définir des valeurs de pointeur sur autre chose que l'allocation de mémoire qui vous a été donnée.
Une raison importante pour laquelle de nombreux systèmes d'exploitation utilisent tous les bits zéro pour la représentation du pointeur nul, c'est que cela signifie memset(struct_with_pointers, 0, sizeof struct_with_pointers)
et similaire définira tous les pointeurs à l'intérieur de struct_with_pointers
aux pointeurs nuls. Ce n'est pas garanti par la norme C, mais de nombreux programmes l'assument.
Cela doit avoir une certaine valeur. De toute évidence, vous ne voulez pas marcher sur des valeurs que l'utilisateur pourrait légitimement vouloir utiliser. Je suppose que puisque le runtime C fournit le segment BSS pour les données initialisées à zéro, il est un certain sens d'interpréter zéro comme une valeur de pointeur non initialisée.
Le choix de la valeur sentinelle est arbitraire, et cela est en fait traité par la prochaine version de C++ (connue officieusement sous le nom de "C++ 0x", très probablement connue à l'avenir sous le nom ISO C++ 2011) avec l'introduction du mot-clé nullptr
pour représenter un pointeur de valeur nulle. En C++, une valeur de 0 peut être utilisée comme expression d'initialisation pour tout POD et pour tout objet avec un constructeur par défaut, et elle a la signification particulière d'assigner la valeur sentinelle dans le cas d'une initialisation de pointeur. Quant à savoir pourquoi une valeur négative n'a pas été choisie, les adresses vont généralement de 0 à 2N-1 pour une valeur N. En d'autres termes, les adresses sont généralement traitées comme des valeurs non signées. Si la valeur maximale était utilisée comme valeur sentinelle, elle devrait alors varier d'un système à l'autre en fonction de la taille de la mémoire, tandis que 0 est toujours une adresse représentable. Il est également utilisé pour des raisons historiques, car l'adresse mémoire 0 était généralement inutilisable dans les programmes, et de nos jours la plupart des systèmes d'exploitation ont des parties du noyau chargées dans les pages de mémoire inférieures, et ces pages sont généralement protégées de telle manière que si touché (déréférencé) par un programme (enregistrer le noyau) provoquera une erreur.
Dans l'une des anciennes machines DEC (PDP-8, je pense), le runtime C protégerait la mémoire de la première page de mémoire de sorte que toute tentative d'accès à la mémoire dans ce bloc provoquerait une exception.
Il y a déjà beaucoup de bonnes réponses dans ce fil; il existe probablement de nombreuses raisons différentes pour préférer la valeur 0
pour les pointeurs nuls, mais je vais en ajouter deux autres:
La valeur 0
est une valeur spéciale qui prend différentes significations dans des expressions spécifiques. Dans le cas des pointeurs, comme cela a été souligné à maintes reprises, il est probablement utilisé parce qu'à l'époque, c'était la façon la plus pratique de dire "insérez ici la valeur sentinelle par défaut". En tant qu'expression constante, elle n'a pas la même signification que zéro au niveau du bit (c'est-à-dire que tous les bits sont mis à zéro) dans le contexte d'une expression de pointeur. En C++, il existe plusieurs types qui n'ont pas de représentation zéro au niveau du bit de NULL
, comme le pointeur membre et le pointeur sur la fonction membre.
Heureusement, C++ 0x a un nouveau mot clé pour "expression qui signifie un pointeur invalide connu qui ne correspond pas également à zéro au niveau du bit pour les expressions intégrales": nullptr
. Bien qu'il existe quelques systèmes que vous pouvez cibler avec C++ qui permettent le déréférencement de l'adresse 0 sans barfing, faites attention au programmeur.
Cela dépend de l'implémentation de pointeurs en C/C++. Il n'y a aucune raison spécifique pour laquelle NULL est équivalent dans les affectations à un pointeur.
Un système d'exploitation vous permet rarement d'écrire à l'adresse 0. Il est courant de coller des éléments spécifiques au système d'exploitation dans une mémoire faible; à savoir, les IDT, les tableaux de pages, etc. (Les tableaux doivent être en RAM, et il est plus facile de les coller en bas que d'essayer de déterminer où se trouve le haut de RAM.) Et aucun OS dans son bon sens ne vous permettra d'éditer les tables système à volonté.
Cela n'était peut-être pas dans l'esprit de K&R quand ils ont fait C, mais cela (avec le fait que 0 == null est assez facile à retenir) fait de 0 un choix populaire.