-3
votes

Est-ce que (p + x) -x donne toujours à P pour Pointeur P et entier X dans GCC Linux X86-64 C ++

Supposons que nous ayons: xxx

comme récemment discuté dans une autre question , arithmétique < EM> Y compris les opérations de comparaison sur des pointeurs non valides peuvent générer un comportement imprévu dans GCC Linux X86-64 C ++. Cette nouvelle question concerne spécifiquement l'expression (p + x) -x : peut-il générer un comportement inattendu (c.-à-d. Résultat non être p ) dans toute version de la GCC existante en cours d'exécution x86-64 Linux?

Notez que cette question concerne simplement l'arithmétique du pointeur; Il n'y a absolument aucune intention de accès l'emplacement désigné par * (p + x) , qui serait évidemment imprévisible en général.

L'intérêt pratique Voici tableaux non zéro . Notez que (p + x) et la soustraction par x se produisent dans différents endroits dans le code dans ces applications.

Si les versions de GCC récentes sur x86 -64 On peut montrer qu'il ne peut jamais générer de comportement inattendu pour (p + x) -x -x puis ces versions peuvent être certifiées pour les tableaux non nulles et les futures versions générant un comportement inattendu pourraient être modifiées ou configurées. Pour soutenir cette certification.

update

pour le cas pratique décrit ci-dessus, nous pourrions également supposer p est un fichier valide Pointeur et p! = null .


20 commentaires

"Peut générer UB dans GCC Linux x86-64 C ++" - il ne génère ub. Il est UB dans la langue C ++ la langue - le compilateur et la plate-forme ne sont pas pertinents dans la mesure où il s'agit d'UB ou non.


"Spécifiquement, si les versions de GCC récentes sur X86-64 peuvent être montrées pour ne jamais générer UB pour (p + x) -x, ces versions peuvent être certifiées pour des tableaux non nulles". Je suppose que je suis le seul qui est trop muet pour comprendre cela?


@Johannes Schaub Ce que je voulais dire, c'est si GCC a une excellente fonctionnalité (spécifiquement, les mathématiques sur des pointeurs non valides) qui ne sont pas nommés actuellement, nous pouvons lui donner un nom, puis la demander à de futures versions aussi.


Vous êtes toujours complètement mal compris ce que signifie UB. (Oui, j'ai lu tous vos commentaires dans votre autre question.) L'UB est dans le code source . Il n'est pas généré par le compilateur ou le système.


Il est vrai qu'un compilateur peut définir le comportement des constructions linguistiques que le standard de la langue indique non défini et, en fait, GCC a beaucoup d'extensions qui tombent dans cette catégorie. Mais je ne pense pas que GCC définit un comportement pour l'arithmétique du pointeur qui va au-delà de la définition des normes de la langue.


@prl ok ok j'ai frotté la phrase "UB" de la question. Je voulais dire comportement inattendu . Droite, GCC n'a éventuellement pas donné la fonctionnalité un nom encore ... La question concerne sa mise en œuvre actuelle.


Le GCC est suffisamment complexe qu'il est très difficile de déterminer qu'il ne peut avoir de comportement inattendu dans aucune situation impliquant un comportement non défini. Je ne voudrais certainement pas l'exclure pour les conditions que vous décrivez.


Avez-vous examiné la création d'un type de données abstraite à base non zéro?


@Galik Oui, qui a été discuté ici . Quelqu'un a affiché (et supprimé) une bonne réponse impliquant intPTR_T ; Il y avait d'autres réponses qui n'ont pas atteint l'efficacité souhaitée (un registre pour représenter le tableau). Je pense que la raison pour laquelle la question a été évitée était parce que je n'ai pas spécifié GCC Linux X86-64. Je devrais ouvrir un nouveau ... C ++ Tableaux à base de zéro sur GCC Linux X86-64.


S'il était intéressant de soutenir des tableaux non nulle à base de GCC, il serait sûrement effectué en ajoutant une caractéristique spécifique, non pas en définissant généralement le comportement que vous posez sur.


@prl qui dépend de la question de savoir si les CCG existantes le soutiennent déjà. Si tel est le cas, ils ont le comportement que je demande, sans supporter spécifiquement des tableaux à base de zéro. Donc, il existe potentiellement 2 chemins vers des tableaux non nuls.


Oui, les implémentations actuelles peuvent avoir le comportement que vous souhaitez (bien que je doute de cela), mais les responsables ne vont jamais accepter de le documenter et garantir ainsi un soutien fiable pour cela.


@PRL Out of Curiosity, pourquoi douterez-vous que (p + x) -x se comporterait toujours comme je m'attends? En outre, je suis d'accord avec votre autre point sur la documentation qu'il est improbable, sauf si GCC prend en charge des tableaux non liés à la base zéro, mais il y a un problème de poulet et d'œuf: Si personne n'admet d'utiliser la fonctionnalité, c'est certain < / I> Ne pas être documenté. Vous pouvez peut-être créer une version appropriée avec intptr_t mais qui a peut-être ses propres risques (Typecasts ...).


@personal_cloud qui ne ressemble pas à un type de données abstraite, davantage comme traiter avec un pointeur embarqué. Avez-vous même besoin d'utiliser un pointeur du tout? Vous ne pouvez pas simplement utiliser un objet de classe avec opérateur [] défini?


@Galik sûr, mais dans la mise en œuvre de cette classe, comment effectuez-vous le fonctionnement TRAY [I] lors de l'utilisation d'un seul registre de tableau , si est de la matrice n'est pas basé sur zéro?


Le compilateur devrait faire cela pour vous pour vous en mettant en cache le pointeur interne de la classe 'dans un registre et en faisant les mathématiques de compensation en conséquence avec l'indice offsetté. Je suppose que vous devez faire confiance au compilateur est bon pour optimiser ces choses.


@Galik Dans de nombreux cas, oui je suis d'accord, cela devrait fonctionner. Mais je ne pense pas que le compilateur soit autorisé à changer les structures en mémoire, car il existe des normes de liaison, etc., donc j'ai peur que sur un froid récupération de la classe, il faudrait récupérer les deux < Code> allouat_zero_based_array et le offset et ajoutez-les, pour peupler le cache que vous décrivez.


La CPU doit ajouter quelque chose quel que soit le régime que vous concevez. Comment ajuster l'index (mon schéma) est-il différent de l'ajustement du pointeur (votre schéma?)?


@Galik L'idée est d'utiliser un index existant sur de nombreux tuiles de banlieue, chacune mise en œuvre d'une section d'un grand réseau virtuel. Idée très courante des graphiques. Donc, dans votre schéma, l'index devrait être ajusté séparément pour chaque banalier.


@JESPERJUHL: Votre premier commentaire est une légère surestimation pour l'affaire Général: il est possible que la mise en œuvre de C de définition du comportement que ISO C feuille non définie. par exemple. GCC -FWAPV Définit le débordement signé sous forme d'enveloppe de complément 2. GCC -FNO-strict-aliasing-aliasing définit le comportement de uint32_t float_bits = * (int *) & my_float; . Mais dans ce cas particulier, GCC ne sort pas de sa manière de définir le comportement des mathématiques sur des pointeurs en dehors des objets. Cela fonctionne généralement comme vous vous attendez de toute façon, mais cela ne signifie pas que ce n'est pas techniquement ub. (Comme je répondis à la question précédente de l'OP)


3 Réponses :


1
votes

Voici une liste des extensions de GCC. https://gcc.gnu.org/onlinedocs/gcc/c-extensions. HTML

Il y a une extension pour l'arithmétique du pointeur. GCC permet d'effectuer un pointeur arithmétique sur des pointeurs de vide. (Pas l'extension que vous demandez.)

Ainsi, GCC traite le comportement de l'arithmétique du pointeur que vous posez comme non défini dans les mêmes conditions que décrites dans la norme linguistique.

Vous pouvez examiner là-bas et voir s'il y a quelque chose que j'ai manqué qui est pertinent pour votre question.


5 commentaires

Merci d'avoir partagé la liste. Pouvez-vous expliquer pourquoi un "p + x) -x évalue à la fonctionnalité de P" devrait être une extension ? N'est-il pas possible que le moteur principal a déjà cette fonctionnalité? Après tout, C ++ ne le refuserait pas. (En fait, la spécification C ++ rendrait difficile la mise en œuvre de la fonctionnalité de la fonctionnalité de X86-64. Un compilateur aurait une période difficile déterminant que p est ni Un emplacement de mémoire ni un élément de tableau ni dans une page allouée)


Si cela n'est pas documenté comme une extension, il s'agit de l'UB dans les mêmes conditions que dans la norme. Et si c'est UB, le compilateur est probable dans certaines situations de générer du code qui a un "comportement inattendu".


"GCC a un comportement indéfini". Pouvez-vous s'il vous plaît expliquer ce que cela signifie? Le projet GCC définit-il le terme quelque part? Notez que nous sommes dans la zone d'avocat-avocat-troll ici, donc je ne suis pas sûr que cela soit acceptable sans définition!


@Johannes, je veux dire que si la norme indique que le comportement est indéfini et que GCC ne le définit pas lui-même, puis GCC traite le comportement comme non défini.


J'aimerais toujours savoir pourquoi vous doutez que la mise en œuvre actuelle ait la propriété que j'attends, mais je dois accepter votre réponse comme étant valide et pertinente basée sur une recherche raisonnable au premier niveau de la documentation. J'apprécie vos idées sur le sujet. Merci de les avoir partagés.



1
votes

Vous ne comprenez pas ce que "comportement non défini" est, et je ne peux pas vous blâmer, étant donné que cela est souvent mal expliqué. C'est ainsi que la norme définit le comportement non défini, section 3.27 dans intro.defs:

comportement pour lequel ce document impose aucune exigence

C'est ça. Rien de moins, rien de plus. La norme peut être considérée comme une série de contraintes pour les fournisseurs de compilateur à suivre lors de la génération de programmes valides. Quand il y a un comportement indéfini, tous les paris sont éteints.

Certaines personnes disent que le comportement indéfini peut conduire à votre programme reproducteur de dragons ou à reformater votre disque dur, mais je trouve que c'est un peu de paille. Plus de manière réaliste, quelque chose comme passer des extrémités des limites d'un tableau peut aboutir à un défaut SEG (en raison de la déclenchement d'une faute de page). Parfois, un comportement indéfini permet aux compilateurs de faire des optimisations pouvant changer le comportement de votre programme de manière inattendue, car rien ne dit que le compilateur ne peut pas . .

Le point est que les compilateurs ne "génèrent pas de comportement indéfini". Le comportement non défini existe dans votre programme.

Ce que je voulais dire est, si GCC a une excellente fonctionnalité (spécifiquement, les mathématiques sur des pointeurs non valides) qui ne sont pas nommés actuellement, nous pouvons lui donner un nom, puis la demander à de futures versions aussi.

Ce serait alors une extension non standard et on pourrait s'attendre à ce qu'il soit documenté. Je doute également d'qu'une telle caractéristique serait à forte demande étant donné que cela permettrait non seulement aux gens d'écrire un code dangereux, mais il serait extrêmement difficile de générer des programmes portables pour.


2 commentaires

Oui, je sais ce que UB signifie en général. Pour cette question, j'ai précisé que je voulais écrire comportement inattendu que je définis comme (p + x) -x n'entraîne pas p ( Ce que doit très clair du titre de la question) et dans une implémentation très spécifique de C ++, à savoir GCC Linux X86-64.


Le comportement non défini peut conduire à votre programme Swawning Dragons Pour être juste, vous devez disposer d'un adaptateur de frai de dragon installé dans le système.



1
votes

Oui, pour GCC5.x et plus tard spécifiquement, cette expression spécifique est optimisée très tôt à juste p code>, même avec optimisation désactivée, quelle que soit toute éventuelle d'exécution UB.

Cela se produit même avec une matrice statique et une taille constante du temps de compilation. gcc -fsanitize = non défini code> n'insère pas d'instrumentation à la recherche non plus. Aussi aucun avertissement à -wall -wextra -wpedantic code> p> xxx pré>

à l'aide de gcc -dump-arbores-original-original - Original code> pour vider son Représentation interne de la logique du programme Avant toute optimisation ne montre que cette optimisation s'est produite même avant que dans GCC5.x et plus récent STRAND>. (Et arrive même à -o0 code>). P> xxx pré>

c'est du godbolt Compiler Explorer avec GCC8.3 avec -O0 code>. p>

La sortie ASM X86-64 est juste: p>

return <retval> = p + ((sizetype) ((long unsigned int) x * 4) + -(sizetype) ((long unsigned int) x * 4));


10 commentaires

Merci de m'avoir appris sur -fdump-arbores-original ; c'est vraiment cool. Quoi qu'il en soit, oui, j'invite que le (p + x) et la soustraction puisse se produire à différents endroits, comme dans la question de pointeur de tableau non nulle; J'ai mis à jour la question. Et oui, je cherchais quels types d'optimisations que le compilateur pourrait concevoir cela permettrait de casser (p + x) -x == p . Votre exemple de préfaquage est concevable (et, comme vous le dites, improbable).


@Personal_Cloud: Je ne voulais pas avoir pour la préfaquage, je voulais vous vectoriser automatiquement une boucle de recherche comme tandis que (* p ++! = 0) {} (c.-à-d. SHLEN) en utilisant des charges 16 octets et PCMPEQB / PMOVMSKB / TEST + JZ . Donc, vous touchez inévitablement les données au-delà de la terminaison 0 octet, à moins que cela ne soit à la fin d'un vecteur. Pour que cela soit sûr, vous devez aligner votre pointeur afin que vous ne compromettez pas d'octets qui ne contiennent aucun octet que vous êtes autorisé à lire, et peut donc être désagréable. est-il sans danger de lire après la fin d'un tampon dans la même page sur X86 et X64? .


Si le compilateur "sait", il est sûr, cela pourrait potentiellement décider d'utiliser des charges non alignées à partir du point de départ, et peut donc segfault si le terminateur est le dernier octet d'une page. (Mais oui, il est peu probable, car il doit expliquer le cas où le terminateur n'est pas trouvé et qu'il continue à lire au-delà de cette distance sûre-sécurité.) Outre le fait que GCC / Clang ne vectorise jamais automatiquement au début de la boucle. n'est pas connu avant que la première itération fonctionne. ICC va, cependant.


Je vois. Si concevable, si j'ai un faux pointeur q = (p + 100) et plus loin, je fais une boucle de recherche à partir de q [-100] , le compilateur regarde que Et pense qu'il peut charger jusqu'à ce que q , qui traverse une limite de page (car p n'était que la taille 32, dis) et segfault sur 1% des courses .. . Plausible.


@personal_cloud: Oui, exactement. Je pense que les règles ISO C UB permettent cela.


J'aime vraiment cette réponse parce que cela répond fondamentalement à la question (comme un "oui, très probable, (p + x) -x == p ") et illustre également ce genre de ub pourrait résulter même Si l'arithmétique lui-même est correct - montrant essentiellement pourquoi je devrais vous soucier des effets secondaires UB, pas seulement le résultat direct du calcul causant UB.


Dans le même temps, vous devriez vous demander pourquoi une telle optimisation serait écrite pour tirer parti de cela ou de ce pointeur qui se trouve juste dans le programme ... S'appuyant sur la limite de 4K de la page semblerait fonctionner autant plus souvent. Néanmoins, d'autres ont fait que GCC est très compliqué et ajoutant des optimisations difficiles tout le temps. Je suis d'abord couru dans ce genre de chose sur Cette question qui s'est avérée être un mauvais type de jeu de mots sur bool s. (Seuls les types spécifiques sont bons pour memcpy -itant des opérations ... et non bool ...)


@personal_cloud: Yup, c'est ce que tout le monde vous a dit: UB ne veut pas dire que vous pouvez obtenir la mauvaise réponse pour un test simple, cela signifie vraiment tout sinon dans votre programme complet peut casser aussi, éventuellement de manière subtile donnée à certains cas d'angle. Celui-ci est assez obscur, cependant, et des implémentations de qualité telles que GCC pourraient intentionnellement pas optimiser en fonction de cette UB, même si elles le pouvaient. Il est probablement moins connu que le débordement signé. C'est l'exemple classique, par exemple blog.llvm.org/2011/05/ ce qu'estvery-c-programmer-should-klow.ht ml (à propos de UB)


@personal_cloud: Ouais, un aliasing strict est l'autre type de UB bien connu que les compilateurs autres que MSVC optimisent, et ont été depuis de nombreuses années. Intéressant que seul GCC5 et plus récent brise réellement ce code de buggy. En outre, cela prouve définitivement que la coulée d'un pointeur sur un __ m256i n'est pas sûr. __ m256i * est comme char * et peut alias quelque chose, mais cela ne fonctionne pas dans l'autre sens. C'est un exemple utile, car les cas triviaux fonctionnent souvent de toute façon.


Je pense que -fsanitize = pointeur-overflow est censé instrument cela, il ressemble à un bug qu'il ne désactive pas l'optimisation.