Évaluation de la condition contenant le pointeur unitaire – UB, mais peut-il se bloquer?

Quelque part sur les forums, j’ai rencontré ceci:

Any attempt to evaluate an uninitialized pointer variable invokes undefined behavior. For example: int *ptr; /* uninitialized */ if (ptr == NULL) ...; /* undefined behavior */ 

Que veut-on dire ici? Est-ce que cela signifie que si j’écris UNIQUEMENT:

 if(ptr==NULL){int t;}; 

cette déclaration est déjà UB? Pourquoi? Je ne suis pas en train de déréférencer le pointeur, n’est-ce pas? (J’ai remarqué qu’il y avait peut-être un problème de terminologie, par UB dans ce cas, j’ai mentionné: mon code plantera-t-il JUST juste à cause de la vérification if?)

L’utilisation de variables unitialisées appelle un comportement indéfini . Peu importe qu’il s’agisse d’un pointeur ou non.

 int i; int j = 7 * i; 

est indéfini aussi. Notez que “non défini” signifie que tout peut arriver, y compris la possibilité que cela fonctionne comme prévu.


Dans ton cas:

 int *ptr; if (ptr == NULL) { int i = 0; /* this line does nothing at all */ } 

ptr peut contenir n’importe quoi, il peut s’agir d’une corbeille aléatoire, mais il peut aussi être NULL . Ce code ne plantera probablement pas, car vous comparez la valeur de ptr à NULL . Nous ne soaps pas si l’exécution entre dans le corps de la condition ou non, nous ne pouvons même pas être sûrs qu’une valeur sera lue avec succès – et par conséquent, le comportement n’est pas défini .

votre pointeur n’est pas initialisé. Votre déclaration serait la même que:

 int a; if (a == 3){int t;} 

puisque a n’est pas initialisé; sa valeur peut être n’importe quoi, donc vous avez un comportement indéfini. Peu importe que vous déréférenciez votre pointeur ou non. Si vous faites cela, vous obtiendrez une erreur de segmentation

Le projet de norme C99 indique qu’il est clairement défini dans l’ Annex J.2 Comportement non défini:

La valeur d’un object à durée de stockage automatique est utilisée tant qu’il est indéterminé (6.2.4, 6.7.8, 6.8).

et le texte normatif a un exemple qui dit aussi la même chose dans la section 6.5.2.5 composés, paragraphe 17 qui dit:

Notez que si une instruction d’itération était utilisée à la place d’un goto explicite et d’une instruction étiquetée, la durée de vie de l’object non nommé serait le corps de la boucle uniquement, et lors de la prochaine entrée autour de p aurait une valeur indéterminée, ce qui aurait pour résultat comportement indéfini.

et le projet de norme définit le comportement non défini comme suit:

comportement, lors de l’utilisation d’une construction de programme non portable ou erronée ou de données erronées, pour lequel la présente Norme internationale n’impose aucune exigence

et note que:

Le comportement non défini possible peut aller d’ignorer complètement la situation avec des résultats imprévisibles, de se comporter pendant la traduction ou l’exécution du programme d’une manière documentée caractéristique de l’environnement (avec ou sans émission d’un message de diagnostic), en mettant fin à la traduction ou à l’exécution (avec la publication). d’un message de diagnostic).

Comme Shafik l’a fait remarquer, le projet de norme C99 déclare toute utilisation de variables non personnalisées avec comportement indéfini à durée de stockage automatique. Cela m’étonne, mais c’est comme ça. La raison de mon utilisation du pointeur est présentée ci-dessous, mais des raisons similaires doivent être également valables pour les autres types.

Après int *pi; if (pi == NULL){} int *pi; if (pi == NULL){} votre prog est autorisé à faire des choses arbitraires. En réalité, rien ne se passera sur les PC. Mais il existe des architectures qui ont des valeurs d’adresse illégales, un peu comme les flottants NaN, qui vont provoquer une interruption matérielle lorsqu’elles sont chargées dans un registre. Ce sont pour nous, utilisateurs de PC modernes, des architectures sans précédent, qui ont motivé cette disposition. Cf. Par exemple, comment se produit une interruption matérielle dans un pointeur à trois passes, même si le pointeur n’est jamais déréférencé? .

Le comportement de this n’est pas défini en raison de l’utilisation de la stack pour différents appels de fonction. Lorsqu’une fonction est appelée, la stack s’agrandit pour laisser de la place aux variables comsockets dans son périmètre, mais cet espace mémoire n’est ni effacé ni mis à zéro.

On peut montrer que cela est imprévisible dans le code comme ceci:

 #include  void test() { int *ptr; printf("ptr is %p\n", ptr); } void another_test() { test(); } int main() { test(); test(); another_test(); test(); return 0; } 

Cela appelle simplement la fonction test () plusieurs fois, ce qui permet d’imprimer où «ptr» vit en mémoire. Vous vous attendez peut-être à obtenir les mêmes résultats à chaque fois, mais à mesure que la stack est manipulée, l’emplacement physique de «ptr» a été modifié et les données à cette adresse sont inconnues à l’avance.

Sur ma machine qui exécute ce programme, la sortie est la suivante:

 ptr is 0x400490 ptr is 0x400490 ptr is 0x400575 ptr is 0x400585 

Pour explorer cela un peu plus, considérez les implications possibles pour la sécurité de l’utilisation de pointeurs que vous n’avez pas intentionnellement définis.

 #include  void test() { int *ptr; printf("ptr is %p\n", ptr); } void something_different() { int *not_ptr_or_is_it = (int*)0xdeadbeef; } int main() { test(); test(); something_different(); test(); return 0; } 

Cela aboutit à quelque chose d’indéfini même s’il est prévisible. Il n’est pas défini car, sur certaines machines, cela fonctionnera de la même manière et d’autres, il est possible que cela ne fonctionne pas du tout. Cela fait partie de la magie qui se produit lorsque votre code C est converti en code machine.

 ptr is 0x400490 ptr is 0x400490 ptr is 0xdeadbeef 

Certaines mises en œuvre peuvent être conçues de manière à ce qu’une tentative de conversion de la valeur d’un pointeur non valide puisse entraîner un comportement arbitraire. D’autres implémentations sont conçues de manière à ce qu’une tentative de comparaison d’un object pointeur avec null ne produise jamais autre chose que les rendements 0 ou 1.

La plupart des implémentations ciblent le matériel où les comparaisons de pointeurs comparent simplement des bits sans se demander si ces bits représentent ou non des pointeurs valides. Les auteurs de nombreuses implémentations de ce type ont historiquement jugé tellement évident qu’une comparaison de pointeur sur un tel matériel ne devrait avoir d’effet secondaire que de signaler que les pointeurs sont égaux ou d’indiquer qu’ils sont inégaux et qu’ils se soucient rarement de documenter explicitement un tel comportement.

Malheureusement, il est devenu à la mode que les implémentations «optimisent» agressivement le comportement indéfini en identifiant les entrées qui pousseraient un programme à invoquer UB, en supposant que de telles entrées ne puissent pas se produire, puis en éliminant tout code qui n’aurait aucune pertinence si de telles entrées n’étaient jamais reçues. Le sharepoint vue “moderne” est que, comme les auteurs de la norme se sont abstenus d’exiger des comparaisons sans effets secondaires sur les implémentations lorsqu’une telle exigence imposerait des dépenses importantes, il n’y a aucune raison pour que les compilateurs d’une plate-forme quelconque les garantissent.

Vous ne déréférenciez pas le pointeur, vous ne vous retrouvez donc pas avec une erreur de segmentation. Il ne va pas tomber en panne. Je ne comprends pas pourquoi quiconque pense que comparer deux nombres va planter. C’est n’importe quoi. Donc encore:

Il ne va pas se briser. PÉRIODE.

Mais c’est toujours UB. Vous ne savez pas quelle adresse mémoire le pointeur contient. Cela peut être ou ne pas être NULL. Donc, votre condition if (ptr == NULL) peut être évaluée ou non.

Revenons à ma déclaration IT NE CRASH PAS. Je viens de tester le pointeur allant de 0 à 0xFFFFFFFF sur les plates-formes 32 bits x86 et ARMv6. Il ne s’est pas écrasé.

J’ai également testé les 0..0xFFFFFFFF et 0xFFFFFFFF00000000..0xFFFFFFFFFFFFFFFF sur la plate-forme amd64. Vérification de la gamme complète prendrait quelques milliers d’années, je suppose.
Encore une fois, il ne s’est pas écrasé.

Je défie les commentateurs et les votants descendants de montrer une plate-forme et une valeur là où il se bloque. Jusque-là, je pourrai probablement survivre à quelques points négatifs.

Il existe également un lien SO pour intercepter la représentation, ce qui indique également qu’elle ne va pas planter.