3
votes

Cela ne devrait-il pas donner un avertissement hors limites?

Je pense que ce code devrait avertir d'un accès au tableau hors limites:

int foo() {
  int x[10] = {0};
  int *p = &x[5];
  return p[~0LLU];
}

Je sais que les avertissements hors limites ne sont pas requis par le standard mais les compilateurs les donnent. Je demande s'il serait correct que le compilateur donne un tel avertissement ici.

Une raison pour laquelle ce code devrait être considéré comme bien formé?

c c++

1 commentaires

Les commentaires ne sont pas destinés à une discussion approfondie; cette conversation a été déplacé pour chatter .


3 Réponses :


2
votes

Le langage C n'impose aucune exigence sur la vérification des limites des tableaux. Cela fait partie de ce qui le rend rapide. Cela étant dit, les compilateurs peuvent effectuer et effectuent des vérifications dans certaines situations.

Par exemple, si je compile avec -O3 dans gcc et remplacez return p [~ 0LLU]; code > avec return p [10]; J'obtiens l'avertissement suivant:

int foo() {
  int y[10] = {0};
  int x[10] = {0};
  int z[10] = {0};
  int *p = &x[5];
  printf("&x=%p, &y=%p, &z=%p\n", (void *)x, (void *)y, (void *)z);
  return p[10] + y[0] + z[0];
}

J'obtiens un avertissement similaire si j'utilise -10 comme index:

gcc -g -O3 -Wall -Wextra -Warray-bounds -o x1 x1.c
x1.c: In function ‘foo’:
x1.c:6:10: warning: ‘*((void *)&x+-20)’ is used uninitialized in this function [-Wuninitialized]
   return p[-100];

Il semble donc qu'il puisse avertir des valeurs négatives non valides pour un index de tableau.

Dans votre cas, il semble pour ce compilateur que la valeur ~ 0LLU est convertie en valeur signée aux fins de l'arithmétique du pointeur et est considérée comme -1.

Notez que cette vérification peut être trompée en plaçant d'autres variables initialisées autour de x:

x1.c: In function ‘foo’:
x1.c:6:10: warning: ‘*((void *)&x+60)’ is used uninitialized in this function [-Wuninitialized]
   return p[10];

Ce code ne produit aucun avertissement même si p [10 ] est hors limites.

C'est donc à l'implémentation si elle veut effectuer une vérification hors limites et comment elle le fait.


8 commentaires

Une grande partie de la question est de savoir s'il s'agit d'un débordement ou si la norme impose que cela équivaut à p [-1].


OP: "Je pense que ce code devrait avertir d'un accès au tableau hors limites". OP ne parle pas d'erreur d'exécution, mais plutôt d'erreur de compilation.


Convenez que dans le cas d'OP " ~ 0LLU est converti en une valeur signée aux fins de l'arithmétique du pointeur et est considéré comme -1", mais C n'impose pas cette conversion - il le permet. Sur une autre plate-forme, p [~ 0LLU] tente un accès au tableau avec une grande valeur positive - trop grande pour x [] .


@chux, je ne pense pas que C même permette cette interprétation, sauf dans le sens où le comportement est indéfini, et donc tout peut arriver.


@JohnBollinger a certainement permis à ~ 0LLU d'être un index valide vers un tableau - un jour. Même dans un sens réel en 2019, étant donné que toute la mémoire n'a pas besoin d'être physiquement là pour accéder à un élément. unsigned long long n'est même pas spécifié comme le type entier le plus large disponible. L'inquiétude d'OP concernant ~ 0LLU devrait plutôt être avec UINTMAX_MAX car c'est le (u) intmax_t qui impose certaines limites, pas unsigned long long .


@chux, je dis que C spécifie la sémantique de l'ajout de pointeurs en termes de mathématiques conventionnelles, sans égard au type de données des opérandes. Certes, C ne permet pas d'interpréter ~ 0ULL lui-même comme -1, et il n'autorise pas non plus une telle réinterprétation dans le contexte de l'arithmétique des pointeurs, sauf dans le sens où tout est permis lorsque le comportement n'est pas défini .


@JohnBollinger D'accord .


@dbush Veuillez supprimer cette réponse car elle n'ajoute rien à la question. Il ne s'agit pas de l'intelligence du compilateur, mais de la validité du code. Weather the standard impose une forme de conversion faisant de ~ 0LLU un index valide ou non. Le consensus (voir la discussion) est que ce n'est pas le cas et que le code est invalide en raison du dépassement des limites.



4
votes

Je pense que ce code devrait avertir d'un accès au tableau hors limites:

Un compilateur décent pourrait vous avertir lorsque vous faites cela sur des tableaux non-VLA (gcc ne le fait pas, mais Clang le fait: https://godbolt.org/z/lOvl5n ​​)

Pour cet extrait:

<source>:3:10: warning: array index -1 is past the end of the array (which contains 10 elements) [-Warray-bounds]

  return x[~0LLU];

         ^ ~~~~~

avertissement:

int foo() {
  int x[10] = {0};  
  return x[~0LLU];  // or x[40] to make it simpler, same thing
}

Le compilateur sait qu'il s'agit d'un tableau, connaît la taille et peut donc vérifier les limites si tout est littéral (non- Le tableau VLA et l'index littéral sont les conditions préalables)

Dans votre cas, ce qui "perd" le compilateur est que vous affectez à un pointeur (le tableau se désintègre en un pointeur) / p>

Après cela, le compilateur n'est pas capable de dire l'origine des données, donc il ne peut pas contrôler les limites (même si dans votre cas, le décalage est ridiculement grand / négatif / peu importe). Un outil d'analyse statique dédié pourrait trouver le problème.


13 commentaires

@NathanOliver oui, mais le diagnostic du compilateur pense que c'est de toute façon -1 :)


Je pense que ce truc -1 est un bug. Ou une chose définie par l'implémentation. De plus, -1 ne peut pas être "passé la fin du tableau". On dirait une chose en affichage uniquement


Probablement. Qui s'en soucie: c'est faux exprès. Mais compte tenu des commentaires dans la question, cela semble intéresser beaucoup les gens. Peut-être important pour une question en soi :). Cela dit, notez que vous pouvez transmettre des décalages négatifs aux pointeurs. Et le compilateur n'attend pas 2 ** 64-1 comme décalage. Pas assez de bélier dans une ville de toute façon :)


Par «bogue», je voulais dire un bogue du compilateur.


La question se résume vraiment à la météo ou non ~ 0LLU est -1 ou non. Parce que p [-1] de la question est très bien. Qu'est-ce qui fait que le compilateur de l'exemple ci-dessus pense x [~ 0LLU] == x [1]? Et est-ce juste de le faire?


Non, je pense que c'est une question complètement différente.


@GoswinvonBrederlow re; météo ou non ~ 0LLU vaut -1 ou pas . ~ 0LLU dans un unsigned long long . Il ne peut pas avoir la valeur -1.


@GoswinvonBrederlow p [-1] est très bien, car rien ne dit que p [0] est le début du tableau. La partie du compilateur qui vérifie les limites se convertit probablement en un nombre signé simplement pour éviter d'avoir 2 chemins de code - tout ce qui dépasse 0x8000000000000000 sera hors limites sur tout ordinateur qui sera créé dans notre vie, donc cela ne fait aucune différence pratique.


OMI, cette réponse ce qui "perd" le compilateur est que vous assignez à un pointeur combiné avec @Eric Postpischil comment serait la meilleure réponse.


Re «Le compilateur sait qu'il s'agit d'un tableau, connaît la taille et peut donc vérifier les limites si tout est littéral»: Dans ce cas, le compilateur n'a pas besoin de connaître la taille du tableau. À moins qu'il ne prenne en charge les tableaux avec des éléments ~ 0ULL , p + ~ 0ULL ne pourra jamais avoir de comportement défini par les standards C ou C ++. Peut-être voudriez-vous que le compilateur ne vous avertisse pas si vous supportez le système d'exploitation ou le code bare metal où vous vous attendez à ce que les gens puissent faire des choses funky avec une arithmétique et des pointeurs non signés. Mais, dans le code normal, si quelqu'un ajoute une valeur plus grande que possible à n'importe quel pointeur, vous pouvez avertir.


Je pense que nous pourrions créer 2 questions distinctes ici. Parce que la transformation de "max_uint" en -1 ou non est une bonne question en soi (même si elle n'est pas aussi pratique que "pourquoi le compilateur n'avertit pas"). Toutes les discussions semblent tourner autour de cela. La plupart des compilateurs utilisent probablement -1 (un bogue?) Et cela fonctionne.


@ Jean-FrançoisFabre Quelle est l'autre question? Parce que je pense que la météo dans ce cas (et par quel raisonnement) max_uint se transforme en -1 est la seule question. Si la conversion en signé est valide, le code est valide. Si ce n'est pas valide, c'est un hors-limites.


Voter vers le bas cette réponse car x [~ 0LLU] élimine l'ambiguïté de la question. Ce sera toujours hors limites.



2
votes

Edit: Réécriture complète, avec des guillemets standards:

[dcl.array] [Note: Sauf lorsqu'il a été déclaré pour une classe , l'opérateur indice [] est interprété de telle manière que E1 [E2] est identique à *((E1)+(E2))

[expr.add] Lorsqu'une expression de type intégral est ajoutée ou soustraite à un pointeur, le résultat a le type de l'opérande du pointeur. Si l'expression P pointe sur l'élément x [i] d'un objet tableau x avec n éléments, les expressions P + J et J + P (où J a la valeur j ) pointent vers (éventuellement -hypothétique) élément x [i + j] si 0 ≤ i + j ≤ n ; sinon, le comportement n'est pas défini.

Par conséquent, p [~ 0LLU] est interprété de la même manière que * (p + ~ 0LLU) (comme pour [dcl.array]) où l'expression entre parenthèses pointe vers le élément x [5 + ~ 0LLU] - si l'index est dans la plage valide - (selon [expr.add]). Si l'index n'est pas dans la plage, le comportement n'est pas défini.

Est-ce que 5 + ~ 0LLU est dans la plage d'indices valide? Compte tenu des règles de conversion d'entiers du langage, l'expression affichée semble être bien définie si le type de 5 était un type signé de taille inférieure ou égale à unsigned long long , et dans ce cas l'élément pointé serait x [4] . Cependant, la norme ne définit pas explicitement le type de i et j dans l'expression qui décrit le comportement. Il doit être interprété comme une expression mathématique pure, auquel cas le résultat serait un index non représentable par long long unsigned et certainement supérieur à n et donc un comportement non défini. p>

Étant donné l'interprétation selon laquelle le comportement n'est pas défini, il ne serait pas incorrect pour le compilateur d'avertir. Quoi qu'il en soit, le compilateur n'est pas obligé d'avertir.


6 commentaires

J'ai dit «devrait», pas «doit». gcc, clang et d'autres compilateurs donnent un tel avertissement. Modification de la question pour clarifier.


L'ajout de 5 + ~ 0LLU a du sens d'être 4 si le 5 était d'un type de rang inférieur à unsigned long long tel qu'il est dans une expression entière. Pourtant, 5 ici n'est pas défini d'un type de rang inférieur - il n'a aucun type prescrit. En termes de calcul du pointeur, il peut être d'un «type» plus large que le unsigned long long et donc 5 + ~ 0LLU est une grande valeur positive.


@chux En effet. Ainsi la conclusion que le comportement est indéfini.


Tout simplement parce que p == x + 5 ne signifie pas nécessairement que p + ~ 0LLU == x + 5 + ~ 0LLU . Chaque sous-expression doit avoir un comportement défini de manière indépendante avant que l'expression globale ne le fasse, donc vous demandez la question d'origine.


De plus, l'opérateur d'addition associe de gauche à droite, mais dans l'ensemble n'est pas associatif au sens mathématique, donc x + 5 + ~ 0LLU serait évalué comme (x + 5) + ~ 0LLU , qui a le même problème d'indéfini que p + ~ 0ULL . Le comportement de x + (5 + ~ 0LLU) n'est pas obligé d'être équivalent, et que dans ce cas, cette dernière expression a défini un comportement n'est pas pertinent.


Le texte standard dit i + j , pas i + j comme vous l'avez mis dans votre citation - il me semble donc qu'il s'agit de la valeur mathématique plutôt que du expression typée. 0 ≤ i + j ≤ n n'est certainement pas une expression valide.