Pourquoi l'adresse zéro est-elle utilisée pour le pointeur nul?

121

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; quand j'appelle malloc, il renvoie un pointeur avec la valeur zéro s'il ne peut pas me procurer 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 mémoire commence à 0, 0 n'est-il pas seulement une adresse valide comme une autre? Comment utiliser 0 pour gérer les pointeurs nuls si tel est le cas? Pourquoi un nombre négatif n'est-il pas nul à la place?


Éditer:

Un tas de bonnes réponses. Je vais résumer ce qui a été dit dans les réponses exprimées comme mon propre esprit l'interprète et j'espère que la communauté me corrigera si je me trompe.

  • Comme tout le reste de la programmation, c'est une abstraction. Juste une constante, pas vraiment liée à l'adresse 0. C ++ 0x met l'accent sur cela en ajoutant le mot-clé nullptr.

  • Ce n'est même pas une abstraction d'adresse, c'est la constante spécifiée par le standard C et le compilateur peut la traduire en un autre nombre tant qu'il s'assure qu'elle n'est jamais égale à une adresse "réelle", et égale d'autres pointeurs nuls si 0 n'est pas le meilleure valeur à utiliser pour la plate-forme.

  • Au cas où ce ne serait pas une abstraction, ce qui était le cas dans les premiers jours, l'adresse 0 est utilisée par le système et interdite au programmeur.

  • Ma suggestion de nombre négatif était un peu de 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 de valeurs est divisé également entre des entiers positifs qui font des adresses valides et des nombres négatifs qui sont juste gaspillés.

  • Si un nombre est toujours représentable par un type de données, il est 0. (Probablement 1 l'est 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 de deux bits qui serait [-2, 1]. Mais alors vous pourriez simplement choisir 0 étant nul et 1 étant le seul octet accessible en mémoire.)

Il y a toujours quelque chose qui n'est pas résolu dans mon esprit. Le pointeur de question de débordement de pile vers une adresse fixe spécifique me dit que même si 0 pour le pointeur nul est une abstraction, les autres valeurs de pointeur ne le sont pas nécessairement. Cela m'amène à publier une autre question Stack Overflow, pourrais-je jamais vouloir accéder à l'adresse zéro? .

Joël
la source
11
Vous pouvez tout aussi facilement changer if (p != 0)pour if (p)lequel est un idiome courant en C et C ++, même si vous devrez sortir de cette habitude si vous prenez Java.
Paul Tomblin
14
Supprimer quelque chose deux fois signifie que votre code est erroné. Je déconseille de définir les pointeurs sur null après pour que vous puissiez planter et résoudre le problème, pas le supprimer. Dans tous les cas, vous faites l'erreur de supposer qu'une adresse est un entier. Ce n'est pas nécessairement vrai et 0 représente simplement une valeur de pointeur réelle qui est spécifique à l'implémentation. Une «adresse négative» n'a pas de sens, conceptuellement.
GManNickG
6
@GMan: Peut-être même une bonne idée de placer le pointeur sur une adresse qui forcera un crash, comme 0xDEADBEEF.
Billy ONeal
5
La question qui ne mourra jamais!
8
@Noah: Le point est défini sur null -> masquer les erreurs de programmation, ne pas définir sur null -> rechercher les erreurs de programmation. Je ne sais pas pour vous, mais j'aimerais que mon code soit correct.
GManNickG

Réponses:

65

2 points:

  • seule la valeur constante 0 dans le code source est le pointeur nul - l'implémentation du compilateur peut utiliser la valeur qu'elle veut ou dont elle a besoin dans le code en cours d'exécution. Certaines plates-formes ont une valeur de pointeur spéciale qui est «invalide» que l'implémentation peut utiliser comme pointeur nul. La FAQ C a une question, "Sérieusement, des machines réelles ont-elles vraiment utilisé des pointeurs nuls différents de zéro, ou des représentations différentes pour des pointeurs vers différents types?" , qui souligne plusieurs plates-formes qui ont utilisé cette propriété de 0 étant le pointeur nul dans la source C tout en étant représentée différemment au moment de 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 produit toujours un pointeur nul,

  • une valeur négative pourrait être tout aussi utilisable par la plate-forme qu'une adresse - le standard 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:

  • il est garanti de comparer inégal à un pointeur vers un objet réel
  • deux pointeurs nuls se compareront égaux (C ++ affine cela de telle sorte que cela ne doit être tenu que pour les pointeurs du même type)
Michael Burr
la source
12
+1 Je suppose que 0 a été choisi uniquement pour des raisons historiques. (0 étant une adresse de départ et invalide, la plupart du temps.) Bien sûr, en général, une telle hypothèse n'est pas toujours vraie, mais 0 fonctionne plutôt bien.
GManNickG
8
L'espace peut également avoir été un facteur contributif. À l'époque où C a été développé pour la première fois, la mémoire était BEAUCOUP plus coûteuse qu'aujourd'hui. Le nombre zéro peut être calculé de manière pratique à l'aide d'une instruction XOR ou sans qu'il soit nécessaire de charger une valeur immédiate. Selon l'architecture, cela pourrait potentiellement économiser de l'espace.
Sparky
6
@GMan - Vous avez raison. Sur les premiers processeurs, l'adresse mémoire zéro était spéciale et offrait une protection matérielle contre l'accès des logiciels en cours d'exécution (dans certains cas, c'était le début du vecteur de réinitialisation, et sa modification pouvait empêcher le processeur de se réinitialiser ou de démarrer). Les programmeurs ont utilisé cette protection matérielle comme une forme de détection d'erreur dans leur logiciel, laissant la logique de décodage d'adresse du CPU vérifier les pointeurs non initialisés ou non valides au lieu d'avoir à dépenser des instructions du CPU pour le faire. La convention demeure à ce jour, même si le but de l'adresse zéro peut avoir changé.
bta
10
Le compilateur Minix 16 bits a utilisé 0xFFFF pour NULL.
Joshua
3
Dans de nombreux systèmes embarqués, 0 est une adresse valide. La valeur -1 (tous les bits un) est également une adresse valide. Les sommes de contrôle pour les ROM sont difficiles à calculer lorsque les données commencent à l'adresse 0. :-(
Thomas Matthews
31

Historiquement, l'espace d'adressage commençant à 0 était toujours ROM, utilisé pour certains systèmes d'exploitation ou routines de gestion d'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 N'attribuez rien à l'adresse 0.

Aviad P.
la source
6
C'est à peu près tout. C'est par convention historique, et les premières adresses ont été utilisées pour les gestionnaires d'interruption, sont donc inutilisables pour les programmes normaux. De plus, 0 est «vide», ce qui peut être interprété comme aucune valeur / aucun pointeur.
TomTom
15

IIRC, la valeur du "pointeur nul" n'est pas garantie égale à zéro. Le compilateur traduit 0 en n'importe quelle valeur «nulle» appropriée pour le système (qui en 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 avec cette valeur spéciale-0, cela empêche le programmeur de savoir quoi que ce soit sur la représentation mémoire du système. Quant à savoir pourquoi ils ont choisi 0 au lieu de 42 ou quelque chose comme ça, je vais deviner 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 la pratique des traductions comme je le décris a rarement lieu, la langue le permet).

rmeador
la source
5
@Justin: Vous avez mal compris. La constante 0 est toujours le pointeur nul. Ce que @meador dit, c'est qu'il est possible que le pointeur nul (indiqué par la constante 0) ne corresponde pas à l'adresse zéro. Sur certaines plates-formes, la création d'un pointeur nul ( int* p = 0) peut créer un pointeur contenant la valeur 0xdeadbeefou toute autre valeur qu'elle préfère. 0 est un pointeur nul, mais un pointeur nul n'est pas nécessairement un pointeur vers l'adresse zéro. :)
jalf
Un pointeur NULL est une valeur réservée et, selon le compilateur, peut être n'importe quel modèle de bits. Le pointeur NULL ne signifie pas qu'il pointe vers l'adresse 0.
Sharjeel Aziz
3
Mais @Jalf, la constante 0 n'est pas toujours le pointeur nul. C'est ce que nous écrivons lorsque nous voulons que le compilateur remplisse le pointeur nul réel de la plateforme pour nous. En pratique, le pointeur NULL habituellement ne correspond à l'adresse zéro, bien que, et j'interpréter la question de Joel comme demander pourquoi. Il y a supposément un octet de mémoire valide à cette adresse, après tout, alors pourquoi ne pas utiliser une adresse inexistante d'un octet inexistant au lieu de supprimer un octet valide de la lecture? (J'écris ce que j'imagine que Joel pensait, pas une question que je me pose.)
Rob Kennedy
@Rob: en quelque sorte. Je sais ce que vous voulez dire, et vous avez raison, mais moi aussi :) L'entier constant 0 représente le pointeur nul au niveau du code source. La comparaison d'un pointeur nul à 0 donne vrai. L'affectation de 0 à un pointeur définit ce pointeur sur null. 0 est le pointeur nul. Mais la représentation réelle en mémoire d'un pointeur nul peut être différente du modèle zéro bit. (Quoi qu'il en soit, mon commentaire était en réponse au commentaire maintenant supprimé de @ Justin, pas à la question de @ Joel. :)
jalf
@jalf @Rob Vous avez besoin de quelques termes pour clarifier, je pense. :) D'après le §4.10 / 1: "Une constante pointeur nul est une expression constante intégrale rvalue de type entier qui s'évalue à zéro. Une constante pointeur nul peut être convertie en un type pointeur; le résultat est la valeur pointeur nul de ce type et se distingue de toutes les autres valeurs de pointeur vers un objet ou de pointeur vers un type de fonction. "
GManNickG
15

Vous devez mal comprendre la signification de la constante zéro 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 & C ++, les pointeurs peuvent avoir la valeur de pointeur nul réservée . La représentation réelle de la valeur du pointeur nul n'a rien à voir avec les "zéros". Cela peut être absolument tout ce qui convient à une plate-forme donnée. Il est vrai que sur la plupart des plates-formes, la valeur du pointeur nul est représentée physiquement par une valeur d'adresse nulle réelle. Cependant, si sur une certaine plate-forme, l'adresse 0 est réellement utilisée dans un certain but (c'est-à-dire que vous devrez peut-être créer des objets à l'adresse 0), la valeur du pointeur nul sur cette plate-forme sera probablement différente. Elle peut être représentée physiquement comme 0xFFFFFFFFvaleur d'adresse ou comme 0xBAADBAADvaleur 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 nulles par constante 0. Afin d'attribuer une valeur de pointeur nul à un pointeur donné, vous continuerez à utiliser des expressions telles que p = 0. Il est de la responsabilité du compilateur de réaliser ce que vous voulez et de le traduire dans la représentation de valeur de pointeur nul appropriée, c'est-à-dire de le traduire dans le code qui mettra la valeur d'adresse de 0xFFFFFFFFdans le pointeur p, par exemple.

En bref, le fait que vous utilisez 0dans 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 0que vous utilisez dans votre code source n'est que du «sucre syntaxique» qui n'a absolument aucun rapport avec l'adresse physique réelle sur laquelle la valeur du pointeur nul «pointe».

Fourmi
la source
3
<quote> Les pointeurs ne sont pas des objets arithmétiques </quote> L'arithmétique des pointeurs est assez bien définie en C et C ++. Une partie de l'exigence est que les deux pointeurs pointent dans le même composite. Le pointeur nul ne pointe vers aucun composite, donc son utilisation dans des expressions arithmétiques de pointeur est illégale. Par exemple, cela n'est pas garanti (p1 - nullptr) - (p2 - nullptr) == (p1 - p2).
Ben Voigt
5
@Ben Voigt: La spécification du langage définit la notion de type arithmétique . Tout ce que je dis, c'est que les types pointeurs n'appartiennent pas à la catégorie des types arithmétiques. L'arithmétique des pointeurs est une histoire différente et totalement indépendante, une simple coïncidence linguistique.
Du
1
Comment une personne qui lit des objets arithmétiques est-elle censée savoir que cela signifie «au sens des types arithmétiques» et non «au sens des opérateurs arithmétiques» (dont plusieurs sont utilisables sur des pointeurs) ou «au sens de l'arithmétique des pointeurs». En ce qui concerne les coïncidences linguistiques, l'objet arithmétique a plus de lettres en commun avec l' arithmétique du pointeur que les types arithmétiques . Dans le même temps, la norme parle de valeur de pointeur . L'affiche originale signifiait probablement une représentation entière d'un pointeur plutôt qu'une valeur de pointeur , et NULLn'a pas besoin explicitement d'être représentée par 0.
Ben Voigt
Eh bien, par exemple, le terme objets scalaires dans la terminologie C / C ++ est juste un raccourci pour les objets de types scalaires (tout comme les objets POD = objets de types POD ). J'ai utilisé le terme d' objets arithmétiques exactement de la même manière, c'est-à-dire des objets de types arithmétiques . Je m'attends à ce que «quelqu'un» le comprenne de cette façon. Quelqu'un qui ne le fait pas peut toujours demander une clarification.
Du
1
j'ai travaillé sur un système où (en ce qui concerne le matériel) nul était 0xffffffff et 0 était une adresse parfaitement valide
pm100
8

Mais comme l'adressage mémoire commence à 0, 0 n'est-il pas seulement une adresse valide comme une autre?

Sur certains / nombreux / tous les systèmes d'exploitation, l'adresse mémoire 0 est spéciale d'une certaine manière. Par exemple, il est souvent mappé à 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 de 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.

ChrisW
la source
4
J'ai codé sur un appareil où l'adresse zéro était une adresse valide et il n'y avait pas de protection de la mémoire. Les pointeurs nuls étaient également tous bits à zéro; si vous avez accidentellement écrit sur un pointeur nul, vous avez explosé les paramètres du système d'exploitation qui étaient à l'adresse zéro; l'hilarité ne s'ensuivait généralement pas.
MM
1
Oui: sur une CPU x86 en mode non protégé, par exemple, l'adresse 0 est la table des vecteurs d'interruption .
ChrisW
@ChrisW: Sur x86 en mode non protégé, l'adresse zéro en particulier est le vecteur d'interruption de division par zéro, que certains programmes peuvent avoir des raisons tout à fait légitimes d'écrire.
supercat
Même sur les plates-formes où le stockage utilisable commencerait à l'adresse physique, zéro, une implémentation C pourrait facilement soit utiliser l'adresse zéro pour contenir un objet dont l'adresse n'est jamais prise, soit simplement laisser le premier mot de mémoire inutilisé. Sur la plupart des plates-formes, comparer-avec-zéro enregistre une instruction par rapport à comparer-avec-tout-autre, donc même gaspiller le premier mot de stockage serait moins cher que d'utiliser une adresse différente de zéro pour null. Notez qu'il n'y a aucune exigence que les adresses des choses non couvertes par la norme C (par exemple les ports d'E / S ou les vecteurs d'interruption) soient inégales à nul, ni que ...
supercat
... le processus système nul-pointeur accède différemment des autres, donc tous les bits-zéro est généralement une bonne adresse pour "null" même sur les systèmes où l'accès à l'emplacement physique zéro serait utile et significatif.
supercat
5

Je suppose que la valeur magique 0 a été choisie pour définir un pointeur invalide car il pourrait être testé avec moins d'instructions. Certains langages machine définissent automatiquement les indicateurs zéro et signe en fonction des données lors du chargement des registres afin que vous puissiez tester un pointeur nul avec un simple chargement puis des instructions de branche sans faire une instruction de comparaison séparée.

(La plupart des ISA ne définissent des indicateurs que sur les instructions ALU, mais pas sur les charges. Et généralement, vous ne produisez pas de pointeurs via le calcul, sauf dans le compilateur lors de l'analyse de la source C. Mais au moins, vous n'avez pas besoin d'une constante de largeur de pointeur arbitraire pour comparer avec.)

Sur les Commodore Pet, Vic20 et C64, qui étaient les premières machines sur lesquelles j'ai travaillé, la RAM commençait à l'emplacement 0, il était donc totalement valide de lire et d'écrire en utilisant un pointeur nul si vous le vouliez vraiment.

KPexEA
la source
3

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 perdez la moitié de l'espace d'adressage.

Axel Gneiting
la source
Remarque: vous ne perdez pas réellement d'espace d'adressage; vous pouvez obtenir un pointeur vers l' adresse 0 en faisant: char *p = (char *)1; --p;. Puisque le comportement sur un pointeur nul n'est pas défini par la norme, ce système peut avoir en pfait une adresse de lecture et d'écriture 0, un incrément pour donner l'adresse 1, etc.
MM
@MattMcNabb: Une implémentation où l'adresse zéro est une adresse matérielle valide peut parfaitement définir légitimement le comportement de char x = ((char*)0);lire l'adresse zéro et stocker cette valeur dans x. Un tel code produirait un comportement indéfini sur toute implémentation qui ne définissait pas son comportement, mais le fait qu'une norme dise que quelque chose est un comportement indéfini n'interdit en aucun cas aux implémentations d'offrir leurs propres spécifications pour ce qu'il fera.
supercat
@supercat ITYM *(char *)0. C'est vrai, mais selon ma suggestion, l'implémentation n'a pas besoin de définir le comportement de *(char *)0ou de toute autre opération de pointeur nul.
MM
1
@MattMcNabb: Le comportement de char *p = (char*)1; --p;ne serait défini par la norme que si cette séquence avait été effectuée après qu'un pointeur vers autre chose que le premier octet d'un objet ait été converti en an intptr_t, et que le résultat de cette conversion produisait la valeur 1 , et dans ce cas particulier, le résultat de --pdonnerait un pointeur vers l'octet précédant celui dont la valeur du pointeur, lors de la conversion intptr_t, avait donné 1.
supercat
3

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 jamais 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, ainsi la valeur fonctionne bien car elle est interdite aux programmes d'application. L'affectation accidentelle d'une valeur entière à un pointeur est aussi susceptible de finir par zéro que toute autre chose.

George Phillips
la source
3
La raison la plus probable derrière tout cela est que: il est bon marché de distribuer de la mémoire pré-initialisée à zéro et pratique d'avoir des valeurs dans cette mémoire qui représentent quelque chose de significatif comme l'entier 0, la virgule flottante 0,0 et les pointeurs nuls. Les données statiques en C qui sont initialisées à zéro / nul ne doivent occuper aucun espace dans l'exécutable et sont mappées à un bloc rempli de zéro lors du chargement. Le zéro peut également bénéficier d'un traitement spécial dans les langages machine: des comparaisons faciles du zéro comme "branche si égal à zéro", etc. MIPS a même un registre fictif qui n'est qu'une constante zéro.
Kaz
2

Historiquement, la mémoire insuffisante 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, il est toujours une mauvaise idée de définir des valeurs de pointeur sur autre chose que l'allocation de mémoire vous a fourni.

Fred Haslam
la source
2

Concernant l'argument de ne pas définir un pointeur sur null après sa suppression afin que les suppressions futures "exposent les erreurs" ...

Si vous êtes vraiment, vraiment inquiet à ce sujet, une meilleure approche, qui est garantie de fonctionner, consiste à tirer parti de assert ():


...
assert(ptr && "You're deleting this pointer twice, look for a bug?");
delete ptr;
ptr = 0;
...

Cela nécessite une saisie supplémentaire et une vérification supplémentaire pendant les versions 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 des 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 bien pire!) Sur un utilisateur si l'un de ces "bogues" 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 vienne à la conclusion qu'il est considéré comme une `` bonne pratique '' de NE PAS définir les pointeurs sur 0 s'il est possible qu'ils soient envoyés vers free () ou supprimer deux fois. Dans les rares cas où il est possible, il n'est JAMAIS recommandé d'utiliser le comportement indé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 retracer au problème d'origine.

Edward étrange
la source
2

Une raison importante pour laquelle de nombreux systèmes d'exploitation utilisent tous les bits-zéro pour la représentation du pointeur nul, est que cela signifie memset(struct_with_pointers, 0, sizeof struct_with_pointers)et similaire définira tous les pointeurs à l'intérieur struct_with_pointerssur des pointeurs nuls. Ceci n'est pas garanti par le standard C, mais de très nombreux programmes l'assument.

zwol
la source
1

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 le déclenchement d'une exception.

Paul Tomblin
la source
Le PDP-8 n'avait pas de compilateur C. Le PDP-11 n'avait aucune protection de mémoire et le VAX était tristement célèbre pour renvoyer silencieusement 0 à des déréférences de pointeur NULL. Je ne sais pas à quelle machine cela fait référence.
fuz
1

Le choix de la valeur sentinelle est arbitraire, et ceci est en fait traité par la prochaine version de C ++ (informellement connue sous le nom de "C ++ 0x", plus susceptible d'être connue à l'avenir sous le nom d'ISO C ++ 2011) avec l'introduction du mot-clé nullptrpour 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'attribuer 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 à 2 N-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 alors 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 la ou les pages inférieures de la mémoire, 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.

Michael Aaron Safyan
la source
1

Il doit avoir une certaine valeur. Évidemment, 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 logique d'interpréter zéro comme une valeur de pointeur non initialisée.

JustJeff
la source
0

Un système d'exploitation vous permet rarement d'écrire à l'adresse 0. Il est courant de garder des éléments spécifiques au système d'exploitation dans une mémoire faible; à savoir, les IDT, les tables de pages, etc. (Les tables 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 la RAM.) Et aucun système d'exploitation sain d'esprit ne vous le permettra modifier les tables système bon gré mal gré.

Ce n'était peut-être pas dans l'esprit de K&R quand ils ont créé C, mais cela (avec le fait que 0 == null est assez facile à retenir) fait de 0 un choix populaire.

cHao
la source
Ce n'est pas vrai en mode protégé, et en fait, sur certaines configurations Linux, vous pouvez écrire à l'adresse virtuelle 0.
L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳
0

La valeur 0est 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 le moyen le 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 NULLcomme le membre pointeur et le pointeur vers la fonction membre.

Heureusement, C ++ 0x a un nouveau mot - clé « expression qui signifie un pointeur invalide connu qui ne correspond pas non plus au niveau du bit zéro 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, alors le programmeur se méfie.

MSN
la source
0

Il y a déjà beaucoup de bonnes réponses dans ce fil; il y a probablement de nombreuses raisons différentes pour préférer la valeur 0des pointeurs nuls, mais je vais en ajouter deux autres:

  • En C ++, l'initialisation à zéro d'un pointeur le définira sur null.
  • Sur de nombreux processeurs, il est plus efficace de définir une valeur à 0 ou de la tester égale / non égale à 0 que pour toute autre constante.
Mark Ransom
la source
0

Cela dépend de l'implémentation des pointeurs en C / C ++. Il n'y a aucune raison spécifique pour laquelle NULL est équivalent dans les affectations à un pointeur.

Nagabhushan Baddi
la source
-1

Il y a des raisons historiques à cela, mais il y a aussi des raisons d'optimisation à cela.

Il est courant que le système d'exploitation fournisse à un processus des pages de mémoire initialisées à 0. Si un programme veut interpréter une partie de cette page de mémoire comme un pointeur, alors il est à 0, il est donc assez facile pour le programme de déterminer que ce pointeur est pas initialisé. (cela ne fonctionne pas très bien lorsqu'il est appliqué à des pages flash non initialisées)

Une autre raison est que sur de nombreux processeurs, il est très très facile de tester l'équivalence d'une valeur à 0. C'est parfois une comparaison gratuite effectuée sans aucune instruction supplémentaire nécessaire, et peut généralement être effectuée sans avoir à fournir une valeur nulle dans un autre registre ou comme un littéral dans le flux d'instructions auquel comparer.

Les comparaisons bon marché pour la plupart des processeurs sont les signés inférieurs à 0 et égaux à 0. (les signés supérieurs à 0 et non égaux à 0 sont implicites par les deux)

Étant donné qu'une valeur sur toutes les valeurs possibles doit être réservée comme mauvaise ou non initialisée, vous pouvez aussi bien en faire celle qui a le test le moins cher d'équivalence à la mauvaise valeur. Ceci est également vrai pour les chaînes de caractères terminées par «\ 0».

Si vous deviez essayer d'utiliser plus ou moins de 0 à cette fin, vous finiriez par couper votre plage d'adresses en deux.

nategoose
la source
-2

La constante 0est utilisée au lieu de NULLparce que C a été faite par des hommes des cavernes il y a des billions d'années, NULL, NIL, ZIPou NADDAaurait tout fait sens beaucoup plus que 0.

Mais comme l'adressage mémoire commence à 0, 0 n'est-il pas seulement une adresse valide comme une autre?

En effet. Bien que de nombreux systèmes d'exploitation vous interdisent de mapper quoi que ce soit à l'adresse zéro, même dans un espace d'adressage virtuel (les gens ont réalisé que C est un langage non sécurisé, et reflétant que les bogues de déréférencement de pointeur nul sont très courants, a décidé de les «corriger» en interdisant le code de l'espace utilisateur à mapper vers la page 0; Ainsi, si vous appelez un rappel mais que le pointeur de rappel est NULL, vous ne finirez pas par exécuter du code arbitraire).

Comment utiliser 0 pour gérer les pointeurs nuls si tel est le cas?

Car 0utilisé en comparaison avec un pointeur sera remplacé par une valeur spécifique à l'implémentation , qui est la valeur de retour de malloc en cas de défaillance de malloc.

Pourquoi un nombre négatif n'est-il pas nul à la place?

Ce serait encore plus déroutant.

L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳
la source
Votre argument concernant les «hommes des cavernes», etc. en est probablement à l'origine, même si je pense que les détails sont différents. Les premières formes de ce qui a évolué en C ont été conçues pour fonctionner sur une architecture particulière où an intn'avait pas seulement la même taille qu'un pointeur - dans de nombreux contextes, un intet un pointeur pouvaient être utilisés de manière interchangeable. Si une routine attendait un pointeur et un autre passé dans un entier 57, la routine utiliserait une adresse avec le même motif binaire que le nombre 57. Sur ces machines particulières, le motif binaire pour désigner un pointeur nul était 0, donc en passant un int 0 passerait un pointeur nul.
supercat du
Depuis ce temps, le C a évolué pour pouvoir être utilisé pour écrire des programmes pour une grande variété d'autres machines avec différentes représentations de nombres et de pointeurs. Alors que les constantes numériques non nulles étaient rarement utilisées comme pointeurs, les zéros numériques constants étaient largement utilisés pour représenter les pointeurs nuls. Interdire une telle utilisation aurait cassé le code existant, donc les compilateurs sont censés traduire un zéro numérique en tout ce que l'implémentation utilise pour représenter un pointeur nul.
supercat du
-4

( Veuillez lire ce paragraphe avant de lire l'article.Je demande à toute personne intéressée par la lecture de cet article d'essayer de le lire attentivement, et bien sûr de ne pas le voter tant que vous ne l'avez pas compris complètement, merci. )

Il s'agit maintenant d'un wiki communautaire, en tant que tel, si quelqu'un n'est pas d'accord avec l'un des concepts, veuillez le modifier, avec une explication claire et détaillée de ce qui ne va pas et pourquoi, et si possible, veuillez citer des sources ou fournir des preuves qui peuvent être reproduites.

Répondre

Voici quelques autres raisons qui pourraient être les facteurs sous-jacents pour NULL == 0

  1. Le fait que zéro soit faux, donc on peut faire directement à la if(!my_ptr)place de if(my_ptr==NULL).
  2. Le fait que les entiers globaux non initiés soient initialisés par défaut à tous les zéros, et en tant que tel un pointeur de tous les zéros serait considéré comme non initialisé.

Ici, je voudrais dire un mot sur d'autres réponses

Pas à cause du sucre syntaxique

Dire que NULL est nul à cause du sucre syntaxique n'a pas beaucoup de sens, si oui, pourquoi ne pas utiliser l'index 0 d'un tableau pour tenir sa longueur?

En fait, C est le langage qui ressemble le plus à l'implémentation interne, est-il logique de dire que C a choisi zéro juste à cause du sucre syntaxique? Ils préfèrent fournir un mot-clé null (comme le font de nombreux autres langages) plutôt que de mapper zéro à NULL!

En tant que tel, alors qu'à partir d'aujourd'hui, il pourrait simplement s'agir de sucre syntaxique, il est clair que l'intention initiale des développeurs de langage C n'était pas pour le sucre syntaxique, comme je le montrerai plus loin.

1) La spécification

Pourtant, s'il est vrai que la spécification C parle de la constante 0 comme pointeur nul (section 6.3.2.3), et définit également NULL comme étant défini par l'implémentation (section 7.19 dans la spécification C11 et 7.17 dans la spécification C99), le il n'en reste pas moins que dans le livre "The C Programming Language" écrit par les inventeurs de C, ce qui suit est indiqué dans la section 5.4:

C garantit que zéro n'est jamais une adresse valide pour les données, donc une valeur de retour de zéro peut être utilisée pour signaler un événement anormal, dans ce cas, pas d'espace.

Le pointeur et les entiers ne sont pas interchangeables, le zéro est la seule exception: le zéro constant peut être attribué à un pointeur, et un pointeur peut être comparé au zéro constant. La constante symbolique NULL est souvent utilisée à la place de zéro, comme mnémonique pour indiquer plus clairement qu'il s'agit d'une valeur spéciale pour un pointeur. NULL est défini dans. Nous utiliserons désormais NULL.

Comme on peut le voir (à partir des mots "adresse zéro") au moins l'intention originelle des auteurs de C était de l'adresse zéro, et non de la constante zéro, il ressort d'ailleurs de cet extrait que la raison pour laquelle la spécification parle du la constante zéro n'est probablement pas d'exclure une expression évaluée à zéro, mais plutôt d'inclure la constante entière zéro comme étant la seule constante entière autorisée pour une utilisation dans un contexte de pointeur sans conversion.

2) Résumé

Alors que la spécification ne dit pas explicitement qu'une adresse zéro peut être traitée différemment de la constante zéro, elle ne dit pas que non, et le fait que lorsqu'elle traite de la constante de pointeur nul, elle ne prétend pas qu'elle est une implémentation définie comme elle fait par la constante définie NULL , prétend à la place qu'elle est zéro, montre qu'il pourrait y avoir une différence entre la constante zéro et l'adresse zéro.

(Cependant, si tel est le cas, je me demande simplement pourquoi NULL est défini par l'implémentation, puisque dans un tel cas, NULL peut également être la constante zéro, car le compilateur doit de toute façon convertir toutes les constantes zéro en l'implémentation réelle définie NULL?)

Cependant, je ne vois pas cela en action réelle, et dans les plates-formes générales, l'adresse zéro et la constante zéro sont traitées de la même manière et lancent le même message d'erreur.

De plus, le fait est que les systèmes d'exploitation actuels réservent en fait toute la première page (plage de 0x0000 à 0xFFFF), juste pour empêcher l'accès à l'adresse zéro à cause du pointeur NULL de C, (voir http://en.wikipedia.org/wiki/ Zero_page , ainsi que "Windows Via C / C ++ par Jeffrey Richter et Christophe Nasarre (publié par Microsoft Press)").

Ainsi, je demanderais à quiconque prétendant l'avoir réellement vu en action, de préciser la plate-forme, et le compilateur, et le code exact qu'il a réellement fait, (bien qu'en raison de la définition vague dans la spécification [comme je l'ai montré] tout compilateur et la plateforme est libre de faire ce qu'il veut).

Cependant, il semble apparemment que les auteurs de C n'avaient pas cela à l'esprit, et ils parlaient de "l'adresse zéro", et que "C garantit que ce n'est jamais une adresse valide", ainsi que "NULL est juste un mnémonique », montrant clairement que son intention initiale n'était pas de« sucre syntaxique ».

Pas à cause du système d'exploitation

Prétendant également que le système d'exploitation refuse l'accès à l'adresse zéro, pour plusieurs raisons:

1) Quand C a été écrit, il n'y avait pas de telle restriction, comme on peut le voir sur ce wiki http://en.wikipedia.org/wiki/Zero_page .

2) Le fait est que les compilateurs C ont accédé à l'adresse mémoire zéro.

Cela semble être le fait de l'article suivant de BellLabs ( http://www.cs.bell-labs.com/who/dmr/primevalC.html )

Les deux compilateurs diffèrent dans les détails dans la façon dont ils gèrent cela. Dans le premier, le début se trouve en nommant une fonction; dans ce dernier, le début est simplement pris à 0. Cela indique que le premier compilateur a été écrit avant que nous ayons une machine avec mappage de mémoire, donc l'origine du programme n'était pas à l'emplacement 0, alors qu'au moment du second, nous avions un PDP-11 qui fournissait une cartographie.

(En fait, à partir d'aujourd'hui (comme je l'ai cité ci-dessus dans les références de wikipedia et de microsoft press), la raison pour laquelle l'accès à l'adresse zéro est restreint est due aux pointeurs NULL de C! Donc, à la fin, il s'avère que c'est l'inverse!)

3) N'oubliez pas que C est également utilisé pour écrire des systèmes d'exploitation, et même des compilateurs C!

En fait, C a été développé dans le but d'écrire le système d'exploitation UNIX avec lui, et en tant que tel, il ne semble pas y avoir de raison pour laquelle ils devraient se limiter à partir de l'adresse zéro.

(Matériel) Explication sur la façon dont les ordinateurs sont (physiquement) capables d'accéder à l'adresse zéro

Il y a un autre point que je veux expliquer ici, comment est-il possible de référencer l'adresse zéro?

Pensez-y une seconde, les adresses sont récupérées par le processeur, puis envoyées sous forme de tensions sur le bus de mémoire, qui est ensuite utilisé par le système de mémoire pour accéder à l'adresse réelle, et pourtant une adresse de zéro signifiera pas de tension , alors comment le matériel physique du système de mémoire accède-t-il à l'adresse zéro?

La réponse semble être, que l'adresse zéro est la valeur par défaut, et en d'autres termes l'adresse zéro est toujours accessible par le système de mémoire lorsque le bus mémoire est complètement éteint, et en tant que telle, toute demande de lecture ou d'écriture sans spécifier une adresse réelle (qui est le cas avec l'adresse zéro) accède automatiquement à l'adresse zéro.

yo hal
la source
1
Je ne vous ai pas contre-voté, mais votre message contient plusieurs inexactitudes factuelles, par exemple. que la mémoire physique à l'offset 0 est impossible d'accès (en raison du fait que tous les commutateurs sont éteints? vraiment?), 0 et la constante 0 étant interchangeables (ils pourraient ne pas l'être) et d'autres.
Hasturkun le
En ce qui concerne 0 et le zéro constant, c'est ce que dit le livre original, et c'est ce que montrent les tests réels, avez-vous trouvé une réelle différence entre les deux? Si oui, quel compilateur et quelle plateforme? Bien que de nombreuses réponses suggèrent qu'il y a une différence, je ne l'ai pas trouvée, et elles n'ont aucune référence pour montrer une différence. En fait d'après en.wikipedia.org/wiki/Zero_page Et aussi "Windows Via C / C ++ par Jeffrey Richter et Christophe Nasarre (publié par Microsoft Press)" toute la première page! est protégé dans les ordinateurs modernes juste pour empêcher null (en fait gaspiller plus d'un octet!)
yoel halb
Bien entendu, la configuration binaire de l'adresse est utilisée pour sélectionner ce qui est lu. C'est généralement le cas. Quoi qu'il en soit, je ne veux pas discuter avec vous, je voulais juste souligner pourquoi vous pourriez avoir été défavorisé.
Hasturkun
Je ne suis pas d'accord avec vos affirmations. Je ne suis pas non plus intéressé à poursuivre cette discussion.
Hasturkun le
6
La revendication matérielle est absurde. Pour lire l'adresse zéro, conduisez! Chip Select bas,! RAS haut,! CAS bas,! WE haut et toutes les lignes d'adresse basses. Lorsque le bus est éteint,! CS est élevé.
MSalters