54 votes

La norme C permet-elle d'attribuer une valeur arbitraire à un pointeur et de l'incrémenter?

Le comportement de ce code est-il bien défini?

 #include <stdio.h>
#include <stdint.h>

int main(void)
{
    void *ptr = (char *)0x01;
    size_t val;

    ptr = (char *)ptr + 1;
    val = (size_t)(uintptr_t)ptr;

    printf("%zu\n", val);
    return 0;
}
 

Je veux dire, pouvons-nous attribuer un nombre fixe à un pointeur et l'incrémenter même s'il pointe vers une adresse aléatoire? (Je sais que vous ne pouvez pas le déréférencer)

67voto

dbush Points 8590

La mission:

void *ptr = (char *)0x01;

Est définie par l'implémentation de comportement , car il est la conversion d'un entier à un pointeur. Ceci est détaillé dans la section 6.3.2.3 de la norme C concernant les Pointeurs:

5 Un entier peut être converti en un pointeur de type. Sauf comme indiqué précédemment, le résultat est la mise en œuvre définies, pourrait ne pas être correctement aligné, pourrait pas à une entité du type référencé, et peut-être un piège de la représentation.

Comme pour la suite de l'arithmétique des pointeurs:

ptr = (char *)ptr + 1;

Cela dépend d'un certain nombre de choses.

Tout d'abord, la valeur courante de ptr peut être un piège de la représentation comme par 6.3.2.3 ci-dessus. Si elle l'est, le comportement est indéfini.

Vient ensuite la question de savoir si l' 0x1 de points à un objet valide. L'ajout d'un pointeur et un entier qui n'est valide que si le pointeur de l'opérande et le résultat de point d'éléments d'un tableau d'objet (un objet unique compte comme un tableau de taille 1), ou d'un élément du passé de l'objet array. Ceci est détaillé dans la section 6.5.6:

7 Pour l'application de ces opérateurs, un pointeur vers un objet qui n'est pas un élément d'un tableau se comporte de la même comme un pointeur vers le premier élément d'un tableau de longueur avec le type de l'objet comme type d'élément

8 Lorsqu'une expression de type entier est ajoutée ou déduite à partir d'un pointeur, le résultat est du type du pointeur l'opérande. Si le pointeur de l'opérande points à un élément d'un tableau objet, et le tableau est assez grand, le résultat des points à un élément décalage de l'élément d'origine, tels que la différence de la les indices de la résultante et du tableau d'origine des éléments est égale à la expression entière. En d'autres termes, si l'expression de P points à l' i-ième élément d'un tableau d'objet, les expressions (P)+N (de manière équivalente, N+(P) ) et (P)-N (où N est la valeur de n ) le point d', respectivement, le i+n-th et th éléments de l'objet tableau, à condition qu'ils existent. En outre, si l'expression de P points pour le dernier élément d'un objet de tableau, l'expression (P)+1 points l'un après le dernier élément de l'objet array, et si l'expression de Q points devant le dernier élément d'un tableau d'objet, l'expression (Q)-1 points à le dernier élément de l'objet array. Si le pointeur de la l'opérande et le résultat de point d'éléments d'un même tableau objet, ou un passé le dernier élément de l'objet tableau, le l'évaluation ne doit pas produire un dépassement de capacité; sinon, le comportement est undefined. Si le résultat des points l'un après le dernier élément de la objet array, il ne doit pas être utilisé comme opérande de unaire * l'opérateur qui est évaluée.

Hébergé sur un de mise en œuvre de la valeur 0x1 presque certainement ne pas pointer vers un objet valide, auquel cas l'addition est pas défini. Un intégré à la mise en œuvre pourrait toutefois en charge la définition des pointeurs à des valeurs spécifiques, et si oui, ce pourrait être le cas que 0x1 n'en fait point d'un objet valide. Si oui, le problème est bien défini, sinon il n'est pas défini.

18voto

Bathsheba Points 23209

Non, le comportement de ce programme n'est pas défini. Une fois indéfini de construire est atteint dans un programme, tout comportement est indéfini. Paradoxalement, tout le comportement passé est undefined.

Le résultat de l' void *ptr = (char*)0x01; la mise en œuvre est définie, en raison, en partie, par le fait qu'un char peut avoir un piège de la représentation.

Mais le comportement de l'découlant de l'arithmétique des pointeurs dans l'énoncé ptr = (char *)ptr + 1; est pas défini. C'est parce que l'arithmétique des pointeurs n'est valable que dans les tableaux, y compris un passé la fin du tableau. À cette fin, un objet est un tableau de longueur un.

8voto

Stephen M. Webb Points 1205

Oui, le code est bien définie comme la mise en œuvre définies. Il n'est pas indéfini. Voir la norme ISO/IEC 9899:2011 [6.3.2.3]/5 et note 67.

Le langage C a été créé à l'origine comme un langage de programmation système. Les systèmes de programmation requise, la manipulation de matériel mappé en mémoire, exigeant que vous auriez des trucs codés en dur adresses dans les pointeurs, parfois incrément de ces pointeurs, et de lire et écrire des données depuis et vers l'résultant de l'adresse. À cette fin, l'affectation et l'entier à un pointeur et la manipulation de pointeur à l'aide de l'arithmétique est bien définie par la langue. En rendant la mise en œuvre définies, ce que la langue permet, c'est que toutes sortes de choses peuvent arriver: de la classique halte-et-catch-feu pour élever un bus d'erreur lorsque vous essayez de déréférencement d'un drôle d'adresse.

La différence entre les comportements indéfinis et définis par l'implémentation de comportement est fondamentalement comportement indéfini signifie "ne le faisons pas, nous ne savons pas ce qui va se passer" et la mise en œuvre définies par le comportement signifie "c'est OK pour aller de l'avant et le faire, c'est à vous de savoir ce qui va se passer."

8voto

Lorehead Points 953

C'est un comportement indéfini.

De N1570 (italiques ajoutés):

Un entier peut être converti en un pointeur de type. Sauf comme indiqué précédemment, le résultat de la mise en œuvre est définie, pourrait ne pas être correctement alignées, peut pas pointer vers une entité du type référencé, et peut-être un piège de la représentation.

Si la valeur est un piège de la représentation, la lecture c'est un comportement indéfini:

Certaines représentations objet n'a pas besoin de représenter une valeur du type d'objet. Si la valeur d'un objet a une telle représentation, et il est lu par une lvalue expression qui n'a pas de type de caractère, le comportement est indéfini. Si une telle représentation est produite par un effet secondaire qui modifie tout ou partie de l'objet par une lvalue expression qui n'a pas de type de caractère, le comportement est indéfini.) Une telle représentation est appelé un piège de la représentation.

Et

Un identificateur est une expression primaire, à condition qu'il ait été déclaré comme désignant un objet (dans ce cas, c'est une lvalue) ou une fonction (dans ce cas c'est une fonction de désignation).

Par conséquent, la ligne void *ptr = (char *)0x01; est déjà potentiellement un comportement indéfini, sur une mise en oeuvre (char*)0x01 ou (void*)(char*)0x01 est un piège de la représentation. Le côté gauche est une lvalue expression qui n'a pas de type de caractère et lit un piège de la représentation.

Sur certains matériels, le chargement d'un pointeur non valide dans une machine registre pourrait planter le programme, c'était donc forcé de passer par le comité de normalisation.

4voto

supercat Points 25534

La Norme n'exige pas que les implémentations processus entier-à-pointeur de conversions de façon significative pour aucune des valeurs entières, ou même pour d'éventuelles entier des valeurs autres que le Pointeur Null Constantes. La seule chose qu'il garantit à propos de ces conversions est qu'un programme qui stocke le résultat de cette conversion directement dans un objet adaptée de type pointeur et ne fait rien avec elle, sauf examiner les octets de cet objet sera, au pire, de voir non spécifié de valeurs. Certes, le comportement de la conversion d'un entier à un pointeur de la mise en Œuvre est Défini, rien n'interdirait toute mise en œuvre (peu importe ce qu'il fait réellement avec de telles conversions!) de préciser que certains (voire tous) les octets de la représentation ayant Quelconque de valeurs, et en précisant que certains (voire tous) les valeurs de nombre entier peut se comportent comme s'ils donnent piège des représentations.

Les seules raisons que la Norme dit rien à propos de entier-à-pointeur de conversions sont les suivantes:

  1. Dans certaines implémentations, la construction est significatif, et certains programmes pour les implémentations de l'exiger.

  2. Les auteurs de cette Norme n'aime pas l'idée d'une construction qui a été utilisé dans certaines implémentations représenterait une violation de contrainte sur les autres.

  3. Il aurait été étrange que le Standard pour décrire une construction, mais alors de préciser qu'il a un Comportement Indéfini dans tous les cas.

Personnellement, je pense que la Norme devrait avoir permis à des implémentations pour traiter entier-à-pointeur de conversions que les violations de contraintes si elles ne définissent pas toutes les situations où ils seraient utiles, plutôt que d'exiger que les compilateurs acceptent le code sans signification, mais ce n'était pas la philosophie à l'époque.

Je pense qu'il serait plus simple de dire simplement que toute opération impliquant entier-à-pointeur de conversions avec autre chose que intptr_t ou uintptr_t valeurs reçues de pointeur vers un entier conversions invoque un Comportement Indéfini, mais ensuite, notez qu'il est commun pour la qualité des implémentations prévu pour la programmation de bas niveau pour traiter un Comportement Indéfini "dans documenté de façon caractéristique de l'environnement". La Norme ne spécifie pas lorsque des mises en œuvre de programmes relatifs au processus qui l'invoque, UB dans ce mode, mais traite plutôt comme une Qualité de mise en Œuvre de problème.

Si une mise en œuvre précise que entier-à-pointeur de conversions de fonctionner dans un mode qui permettrait de définir le comportement de

char *p = (char*)1;
p++;

comme l'équivalent de "char p = (char)2;", puis la mise en œuvre doit s'attendre à travailler de cette façon. D'autre part, une mise en œuvre pourrait définir le comportement d'entier-à-pointeur de conversion de telle manière que:

char *p = (char*)1;
char *q = p;  // Not doing any arithmetic here--just a simple assignment

aurait communiqué nasale démons. Sur la plupart des plates-formes, un compilateur où l'arithmétique sur les pointeurs produit par entier-à-pointeur conversions se comportait bizarrement ne serait pas considéré comme une haute qualité de mise en œuvre adapté pour la programmation de bas niveau. Un programmeur qui n'a pas l'intention de cibler n'importe quel autre type de mises en œuvre pourrait donc s'attendre à de telles constructions à se comporter utilement sur des compilateurs pour lequel le code a été adapté, même si la Norme ne l'exige pas.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X