Le caractère non signé est-il ; a ; comportement indéfini?

Un des exemples de comportement non défini des lectures standard C (J.2):

– Un indice de tableau est hors limites, même si un object est apparemment accessible avec l’indice donné (comme dans l’expression lvalue a [1] [7] étant donné la déclaration int a [4] [5])

Si la déclaration est passée de int a[4][5] à unsigned char a[4][5] , l’access a[1][7] entraîne-t-il toujours un comportement indéfini? Mon opinion est que ce n’est pas le cas, mais d’autres personnes en désaccord m’ont dit quoi que ce soit, et j’aimerais voir ce que d’autres experts en la matière pensent de SO.

Mon raisonnement:

  • Selon l’interprétation habituelle du 6.2.6.1, paragraphes 4 et 6.5, paragraphe 7, la représentation de l’object a est sizeof (unsigned char [4][5])*CHAR_BIT bits et est accessible sous la forme d’un tableau de type unsigned char [20] chevauchant avec l’object.

  • a[1] a le type unsigned char [5] tant que lvalue, mais utilisé dans une expression (en tant qu’opérande pour l’opérateur [] ou de manière équivalente en tant qu’opérande pour l’opérateur + dans *(a[1]+7) ) , il se désintègre en un pointeur de type unsigned char * .

  • La valeur de a[1] est également un pointeur sur un octet de la “représentation” de a sous la forme unsigned char [20] . Interprété de cette façon, append 7 à a[1] est valide.

Je lirais cet “exemple informatif” dans J2 comme une indication de ce que le corps standard voulait: ne vous fiez pas au fait qu’un calcul d’index de tableau donne accidentellement quelque chose dans les limites du “tableau de représentation”. Le but est de s’assurer que toutes les limites de tableaux individuels doivent toujours se trouver dans les plages définies.

Cela permet notamment à une implémentation de faire une vérification agressive des limites et de vous aboyer soit au moment de la compilation, soit au moment de l’exécution si vous utilisez a[1][7] .

Ce raisonnement n’a rien à voir avec le type sous-jacent.

Un fournisseur de compilateur qui souhaite écrire un compilateur conforme est lié à ce que la norme a à dire, mais pas à votre raisonnement. La norme indique qu’un indice de tableau hors limites correspond à un comportement non défini, sans aucune exception , de sorte que le compilateur est autorisé à exploser.

Pour citer mon commentaire de notre dernière discussion ( C99 garantit-il que les tableaux sont contigus? )

“Votre question initiale était pour a[0][6] , avec le caractère de déclaration char a[5][5] . C’est UB, quoi qu’il en soit. Il est valide d’utiliser char *p = &a[3][4]; et l’access p[0] à p[5] . L’adresse &p[6] est toujours valide, mais l’access à p[6] est en dehors de l’object, donc UB. L’access a[0][6] est en dehors de l’object a[0] , qui a le type array [5] of chars. Le type du résultat n’est pas pertinent, il est important de savoir comment vous l’atteindre. ”

MODIFIER:

Il existe suffisamment de cas de comportement non défini pour lesquels vous devez parcourir l’ensemble de la norme, collecter les faits et les combiner pour arriver à la conclusion d’un comportement non défini. Celui-ci est explicite et vous citez même la phrase du Standard dans votre question. Il est explicite et ne laisse aucune place à aucune solution de contournement.

Je me demande simplement à quel sharepoint raisonnement plus explicite attendez-vous de notre part que nous devenions convaincus qu’il s’agit vraiment de UB?

EDIT 2:

Après avoir fouillé dans la norme et recueilli des informations, voici une autre citation pertinente:

6.3.2.1 – 3: Sauf s’il s’agit de l’opérande de l’opérateur sizeof ou de l’opérateur unaire &, ou s’il s’agit d’un littéral utilisé pour initialiser un tableau, une expression de type ” tableau de type ” est convertie en expression avec le type ” pointeur sur type ” qui pointe sur l’élément initial de l’object tableau et n’est pas une valeur lvalue . Si l’object tableau a une classe de stockage de registre, le comportement n’est pas défini.

Donc, je pense que cela est valide:

 unsigned char *p = a[1]; unsigned char c = p[7]; // Ssortingct aliasing not applied for char types 

C’est UB:

 unsigned char c = a[1][7]; 

Car a[1] n’est pas une valeur lvalue à ce stade, mais est évalué plus avant, violant J.2 avec un indice de tableau hors limites. Ce qui se passe réellement devrait dépendre de la manière dont le compilateur implémente réellement l’indexation de tableaux dans des tableaux multidimensionnels. Vous avez donc peut-être raison de dire que cela ne fait aucune différence pour chaque implémentation connue. Mais c’est aussi un comportement indéfini valide. 😉

À partir de 6.5.6 / 8

Si l’opérande de pointeur et le résultat pointent tous deux sur des éléments du même object de tableau , ou d’ un élément passé le dernier élément de l’object de tableau , l’évaluation ne doit pas produire de dépassement de capacité; sinon, le comportement n’est pas défini.

Dans votre exemple, un [1] [7] ne pointe ni vers le même object de tableau, un [1], ni au-delà du dernier élément d’un [1]; il s’agit donc d’un comportement indéfini.

Sous le capot, dans le langage machine actuel, il n’y a pas de différence entre a[1][7] et a[2][2] pour la définition de int a[4][5] . Comme R .. l’a dit, c’est parce que l’access au tableau est traduit en 1 * sizeof(a[0]) + 7 = 12 et 2 * sizeof(a[0]) + 2 = 12 ( * sizeof(int) bien sûr ). Le langage machine ne sait rien des tableaux, masortingces ou index. Tout ce qu’il sait sur les adresses. Le compilateur C ci-dessus peut faire tout ce qu’il veut, y compris la base de vérification des limites naïve sur l’indexeur – a[1][7] serait alors hors limite car le tableau a[1] ne contient pas 8 cellules. À cet égard, il n’y a pas de différence entre int et char ou unsigned char .

Mon hypothèse est que la différence réside dans les règles ssortingctes de crénelage entre int et char – même si le programmeur ne fait rien de mal, le compilateur est obligé de faire un transtypage “logique” pour le tableau, ce qu’il ne devrait pas faire. . Comme Jens Gustedt l’a dit, cela ressemble plus à un moyen d’activer les contrôles de limites ssortingctes, pas à un vrai problème avec int ou char .

J’ai manipulé le compilateur VC ++ et il semble se comporter comme prévu. Quelqu’un peut-il tester cela avec gcc ? D’après mon expérience, gcc est beaucoup plus ssortingct sur ce type de choses.

Je crois que la raison pour laquelle l’exemple cité (J.2) est un comportement indéfini est que l’éditeur de liens n’est pas obligé de mettre les sous-tableaux a [1], a [2], etc. les uns à côté des autres en mémoire. Ils peuvent être dispersés dans la mémoire ou être adjacents mais pas dans l’ordre attendu. Changer le type de base de int à unsigned char ne change rien à cela.