moins que la comparaison pour les pointeurs vides

Je veux comparer deux pointeurs vides comme ceci:

void foo(void* p1, void* p2) { if (p1 < p2) { void *tmp = p1; p1 = p2; p2 = tmp; } // do something with p1 and p2. } 

Est-ce correct selon la norme? Je veux dire, la comparaison des indicateurs de vide est-elle un comportement bien défini?

J’apprécierais que quelqu’un puisse m’indiquer la norme C où cela est documenté.

Comme Drew McGowen l’a souligné dans un commentaire, mais je posterai la citation ici:

6.5.8 Opérateurs relationnels

5 Lorsque deux pointeurs sont comparés, le résultat dépend des emplacements relatifs dans l’espace d’adressage des objects pointés. Si deux pointeurs vers des types d’object pointent tous deux vers le même object ou tous les deux pointent après le dernier élément du même object tableau, ils se comparent égaux . Si les objects pointés sont des membres du même object agrégé, les pointeurs sur les membres de la structure déclarés ultérieurement comparent les pointeurs supérieurs aux membres déclarés précédemment dans la structure, et les pointeurs sur des éléments de tableau avec des valeurs en indice plus grandes comparent les pointeurs supérieurs aux éléments du même tableau. avec des valeurs plus faibles en indice . Tous les pointeurs vers les membres d’un même object d’union se comparent . Si l’expression P pointe sur un élément d’un object tableau et que l’expression Q pointe sur le dernier élément du même object tableau, l’expression du pointeur Q + 1 est supérieure à P. Dans tous les autres cas, le comportement est indéfini .

Cela vient de la norme C11 . C99 est le même.

À partir de C++11 , c’est plus ou moins la même chose. Quelques ajustements à propos des conversions de pointeurs, je peux tout coller si vous le souhaitez. Plus important encore, le comportement est indéterminé (comme Chris l’a souligné plus haut).

Notez que le comportement non défini est mortel. Si vous comparez deux pointeurs indépendants, votre machine risque de prendre feu, de lancer des missiles nucléaires, de faire voler des démons par le nez, etc.

Un comportement non spécifié doit faire quelque chose de vaguement raisonnable. Le compilateur n’a pas à le documenter, ni même faire la même chose pour deux programmes différents, mais il ne peut pas faire exploser le monde. Votre programme est toujours considéré comme valide.

Ainsi, dans votre cas particulier, en compilant en C , l’utilisateur pourrait provoquer un comportement indéfini en fonction de ce qu’il transmet à la fonction. Cela semble assez dangereux.

En outre, contrairement à mon commentaire sur la question, vous ne pouvez pas simplement utiliser != Sur deux void* arbitraires void* s: C11

6.5.9 Opérateurs d’égalité

2 L’un des suivants est valable:

– les deux opérandes sont de type arithmétique;

– les deux opérandes sont des pointeurs vers des versions qualifiées ou non qualifiées de types compatibles;

– un opérande est un pointeur sur un type d’object et l’autre est un pointeur sur une version qualifiée ou non qualifiée de void; ou

– un opérande est un pointeur et l’autre est une constante de pointeur nulle.

La comparaison de deux pointeurs n’est garantie que s’ils pointent sur des parties du même “object” (struct, union ou tableau) (ou un point après la fin pour les tableaux).

En pratique, cela est dû à l’existence d’ordinateurs modèles de mémoire segmentés, où la comparaison du décalage de segment est beaucoup plus rapide que la comparaison du décalage de segment et de l’identificateur de segment. Si lesdits segments se chevauchent, deux pointeurs avec des décalages de segment identiques peuvent se comparer même s’ils pointent vers des zones de mémoire différentes.

De tels systèmes sont moins répandus qu’il ya 20 ans.

Par @drawmcgowen, cela se trouve dans C11 6.5.8.

Bien que les résultats de la comparaison de pointeurs à des objects non liés (pas dans la même struct , union ou tableau) ne soient pas définis, je ne suis pas au courant d’une plate-forme où le comportement non défini est plus que “ne se compare pas dans l’ordre que vous croyez devoir”.

Si vous en avez vraiment besoin et que vous souhaitez limiter les plates-formes sur lesquelles votre code est portable, vous pouvez éventuellement obtenir des garanties raisonnables. Toutefois, sachant que ce comportement n’est pas défini, toute version future de votre compilateur pourrait empêcher ce code de fonctionner correctement.

Ce qui est pire, c’est que certains compilateurs exploitent un comportement non défini pour des opportunités d’optimisation. Par exemple, supposons que vous ayez un tampon et un pointeur au début du tampon.

Si vous comparez un autre pointeur à celui-ci, soit (A) il se trouve dans la mémoire tampon, donc >= , soit (B) il ne se trouve pas dans la mémoire tampon, le résultat est donc indéfini.

Une optimisation simple si vous pouvez prouver qu’un vecteur se trouve au début ou à la fin d’un tampon est de supprimer la comparaison (si >= pour le premier ou <= pour le précédent) et de le remplacer par une constante.

Votre compilateur pourrait le comprendre à tout moment de l'optimisation de votre code, avec n'importe quelle libération ponctuelle.

Il pourrait même dire "ceci est un pointeur sur un object alloué au tas", et prouver que chaque pointeur est égal au pointeur ou non associé - ainsi < et > sont toujours des comportements indéfinis, et les twigs qui le font peuvent être complètement éliminées à partir de votre code.

S'appuyant sur un comportement indéfini signifie que vous devez maintenant auditer le code machine généré par votre code maintenant, et lors de chaque compilation ultérieure.


A l'origine, cette question était marquée avec c ++ . En C ++, il est garanti que std::less()( lhs, rhs ) ordonnera tous les pointeurs. (ceci a été ajouté pour permettre le sorting des pointeurs dans divers conteneurs et algorithmes std ) Ceci peut être utile si vous travaillez dans un système mixte C / C ++.

20.14.6 [comparaisons] / 2

Pour les modèles moins [...], les spécialisations pour tout type de pointeur génèrent un ordre total ssortingct cohérent entre ces spécialisations et conforme à l'ordre partiel imposé par les opérateurs intégrés <[...]. moins de spécialisations si l'opérateur [...] d'appel appelle un opérateur intégré comparant des pointeurs, il génère un ordre total strict cohérent entre ces spécialisations et conforme à l'ordre partiel imposé par ces opérateurs intégrés.

Suite à @BoBTFish … pour les opérateurs relationnels (6.5.8 de C11), les choses sont plus limitées:

Contraintes 2

L’un des suivants doit contenir:

– les deux opérandes ont un type réel;

ou – les deux opérandes sont des pointeurs vers des versions qualifiées ou non qualifiées de types d’object compatibles.

Lequel lit comme excluant void* ( void n’est pas un type, void* n’est donc pas un pointeur sur un type) … donc votre compilateur pourrait bien se plaindre si vous essayez de < etc deux pointeurs void* ou un "real "pointeur et void* . Casting to (char *) fera généralement le travail! Mais comme n'importe quel casting, personne ne va être très compatissant si ça plante et brûle 🙂

BTW, la norme n'est pas une lecture facile, mais à 60 $ c'est un ajout utile à sa bibliothèque