Le comportement de soustraction de deux pointeurs NULL est-il défini?

La différence entre deux variables de pointeur non vides est-elle définie (par C99 et / ou C ++ 98) si les deux valeurs sont NULL ?

Par exemple, disons que j’ai une structure de tampon qui ressemble à ceci:

 struct buf { char *buf; char *pwrite; char *pread; } ex; 

Dites, ex.buf pointe vers un tableau ou une mémoire malloc. Si mon code assure toujours que pwrite et pread pointe dans ce tableau ou un passé, je suis assez confiant que ex.pwrite - ex.pread sera toujours défini. Cependant, si pwrite et pread sont tous deux NULL. Puis-je m’attendre à ce que la soustraction des deux soit définie comme (ptrdiff_t)0 ou le code ssortingctement conforme doit-il tester les pointeurs pour NULL? Notez que le seul cas qui m’intéresse est lorsque les deux pointeurs sont NULL (ce qui représente un casier de tampon non initialisé). La raison est liée à une fonction “disponible” entièrement conforme, compte tenu des hypothèses précédentes:

 size_t buf_avail(const struct s_buf *b) { return b->pwrite - b->pread; } 

En C99, c’est un comportement techniquement indéfini. C99 §6.5.6 dit:

7) Aux fins de ces opérateurs, un pointeur sur un object qui n’est pas un élément d’un tableau se comporte de la même manière qu’un pointeur sur le premier élément d’un tableau de longueur un avec le type de l’object comme type d’élément.

[…]

9) Lorsque deux pointeurs sont soustraits, les deux doivent pointer vers des éléments du même object de tableau, ou vers le dernier élément de l’object de tableau; le résultat est la différence entre les indices des deux éléments du tableau. […]

Et le § 6.3.2.3 / 3 dit:

Une expression de type entier avec la valeur 0, ou une expression de ce type exprimée pour taper void * , est appelée constante de pointeur nul. 55) Si une constante de pointeur nul est convertie en un type de pointeur , il est garanti que le pointeur résultant, appelé pointeur nul , se compare différemment à un pointeur sur un object ou une fonction.

Comme un pointeur nul est différent d’un object, il viole les conditions préalables du 6.5.6 / 9, donc son comportement n’est pas défini. Mais dans la pratique, je serais prêt à parier que presque tous les compilateurs retourneront un résultat de 0 sans effets secondaires.

En C89, c’est aussi un comportement indéfini, bien que la formulation de la norme soit légèrement différente.

C ++ 03, d’autre part, a un comportement défini dans cette instance. La norme fait une exception spéciale pour soustraire deux pointeurs nuls. C ++ 03 §5.7 / 7 dit:

Si la valeur 0 est ajoutée ou soustraite à une valeur de pointeur, le résultat est égal à la valeur du pointeur d’origine. Si deux pointeurs pointent vers le même object ou les deux pointent au-delà de la même masortingce ou les deux sont nuls et les deux pointeurs sont soustraits, le résultat est égal à la valeur 0 convertie au type ptrdiff_t .

C ++ 11 (ainsi que la dernière version de C ++ 14, n3690) ont un libellé identique à C ++ 03, avec seulement le changement mineur de std::ptrdiff_t à la place de ptrdiff_t .

Je l’ai trouvé dans le standard C ++ (5.7 [expr.add] / 7):

Si deux pointeurs […] sont tous deux nuls et que les deux pointeurs sont soustraits, le résultat est égal à la valeur 0 convertie dans le type std :: ptrdiff_t

Comme d’autres l’ont déjà dit, C99 nécessite une addition / soustraction entre 2 pointeurs appartenant au même object tableau. NULL ne désigne pas un object valide, c’est pourquoi vous ne pouvez pas l’utiliser en soustraction.

Edit : Cette réponse n’est valide que pour C, je n’ai pas vu la balise C ++ lorsque j’ai répondu.

Non, l’arithmétique du pointeur n’est autorisée que pour les pointeurs qui pointent dans le même object. Étant donné que, par définition, les pointeurs N standard C ne pointent vers aucun object, il s’agit d’un comportement non défini.

(Bien que je suppose que tout compilateur raisonnable retournera juste 0 , mais qui sait.)

Le standard C n’impose aucune exigence sur le comportement, cependant de nombreuses implémentations spécifient le comportement de l’arithmétique des pointeurs dans de nombreux cas, au-delà des minimums requirejs par la norme, y compris le fait de soustraire un pointeur nul d’un autre. Il y a très peu d’architectures où il devrait y avoir une raison quelconque pour qu’une telle soustraction fasse autre chose qu’un rendement nul, mais malheureusement, il est maintenant à la mode pour les compilateurs d’exiger que les programmeurs écrivent manuellement du code pour gérer les cas de figure que les plates-formes auraient précédemment traitées correctement. Par exemple, si le code censé générer n caractères à partir de l’adresse p est écrit comme suit:

 void out_characters(unsigned char *p, int n) { unsigned char *end = p+n; while(p < end) out_byte(*p++); } 

les compilateurs plus anciens généreraient du code qui ne fournirait aucun résultat fiable, sans effet secondaire, si p == NULL et n == 0, sans nécessité de cas particulier n == 0. Sur les nouveaux compilateurs, cependant, il faudrait append du code supplémentaire:

 void out_characters(unsigned char *p, int n) { if (n) { unsigned char *end = p+n; while(p < end) out_byte(*p++); } } 

qu'un optimiseur peut ou peut ne pas être en mesure de se débarrasser. Ne pas inclure le code supplémentaire peut amener certains compilateurs à comprendre que puisque p "ne peut pas être nul", toute vérification de pointeur NULL ultérieure peut être omise, provoquant ainsi une rupture du code sans rapport avec le "problème" réel.