Arithmétique du pointeur pour le pointeur vide en C

177

Quand un pointeur vers un type particulier ( par exemple int, char, float, ..) est incrémentée, la valeur est augmentée de la taille de ce type de données. Si un voidpointeur qui pointe vers des données de taille xest incrémenté, comment parvient-il à pointer les xoctets en avant? Comment le compilateur sait-il ajouter xà la valeur du pointeur?

Siva Sankaran
la source
3
La question semble supposer que le compilateur (/ run-time) sait quel type d'objet le pointeur a été défini et ajoute sa taille au pointeur. C'est une idée fausse: il ne connaît que l'adresse.
PJTraill
2
"Si un voidpointeur qui pointe vers des données de taille xest incrémenté, comment parvient-il à pointer les xoctets en avant?" Ce n'est pas le cas. Pourquoi les personnes qui ont de telles questions ne peuvent-elles pas les tester avant de les poser - vous savez, au moins au strict minimum où elles vérifient si elles se compilent réellement, ce qui n'est pas le cas. -1, je ne peux pas croire que cela a obtenu +100 et -0.
underscore_d

Réponses:

287

Conclusion finale: l'arithmétique sur a void*est illégale en C et C ++.

GCC l'autorise comme extension, voir Arithmétique sur void- et pointeurs de fonction (notez que cette section fait partie du chapitre "Extensions C" du manuel). Clang et ICC autorisent probablement l' void*arithmétique à des fins de compatibilité avec GCC. D'autres compilateurs (comme MSVC) interdisent l'arithmétique sur void*, et GCC l'interdit si l' -pedantic-errorsindicateur est spécifié, ou si l' -Werror-pointer-arithindicateur est spécifié (cet indicateur est utile si votre base de code doit également compiler avec MSVC).

La norme C parle

Les citations sont tirées du projet n1256.

La description standard de l'opération d'ajout indique:

6.5.6-2: Pour l'addition, soit les deux opérandes doivent être de type arithmétique, soit un opérande doit être un pointeur vers un type d'objet et l'autre doit être de type entier.

Donc, la question ici est de savoir si void*est un pointeur vers un "type d'objet", ou de manière équivalente, s'il voids'agit d'un "type d'objet". La définition du "type d'objet" est:

6.2.5.1: Les types sont divisés en types d'objets (types qui décrivent complètement les objets), types de fonction (types qui décrivent des fonctions) et types incomplets (types qui décrivent des objets mais manquent des informations nécessaires pour déterminer leurs tailles).

Et la norme définit voidcomme:

6.2.5-19: le voidtype comprend un ensemble vide de valeurs; c'est un type incomplet qui ne peut pas être complété.

Puisqu'il voids'agit d'un type incomplet, ce n'est pas un type d'objet. Ce n'est donc pas un opérande valide pour une opération d'addition.

Par conséquent, vous ne pouvez pas effectuer d'arithmétique de pointeur sur un voidpointeur.

Remarques

À l'origine, on pensait que l' void*arithmétique était autorisée, en raison de ces sections de la norme C:

6.2.5-27: Un pointeur vers void doit avoir les mêmes exigences de représentation et d'alignement qu'un pointeur vers un type de caractère.

cependant,

Les mêmes représentation et alignement des exigences sont censées impliquer interchangeabilité comme arguments à des fonctions, des valeurs de retour des fonctions et des membres des syndicats.

Cela signifie donc que cela printf("%s", x)a la même signification, qu'il xsoit de type char*ou void*, mais cela ne signifie pas que vous pouvez faire de l'arithmétique sur un void*.

Note de l'éditeur: Cette réponse a été modifiée pour refléter la conclusion finale.

Sadeq
la source
7
D'après la norme C99: (6.5.6.2) Pour l'addition, soit les deux opérandes doivent être de type arithmétique, soit un opérande doit être un pointeur vers un type d'objet et l'autre doit être de type entier. (6.2.5.19) Le type vide comprend un ensemble vide de valeurs; c'est un type incomplet qui ne peut pas être complété. Je pense que cela montre clairement que l' void*arithmétique des pointeurs n'est pas autorisée. GCC a une extension qui permet de faire cela.
Emploi du
1
si vous ne pensez plus que votre réponse est utile, vous pouvez simplement la supprimer.
caf
1
Cette réponse était utile même si elle s'est avérée erronée car elle contient une preuve concluante que les pointeurs vides ne sont pas destinés à l'arithmétique.
Ben Flynn
1
C'est une bonne réponse, elle a la bonne conclusion et les citations nécessaires, mais les gens qui sont venus à cette question sont venus à la mauvaise conclusion parce qu'ils n'ont pas lu jusqu'au bas de la réponse. J'ai édité ceci pour le rendre plus évident.
Dietrich Epp
1
Clang et ICC n'autorisent pas l' void*arithmétique (du moins par défaut).
Sergey Podobry
61

L'arithmétique des pointeurs n'est pas autorisée sur les void*pointeurs.

Emploi
la source
16
L'arithmétique du pointeur +1 n'est définie que pour les pointeurs vers des types d'objets (complets) . voidest un type incomplet qui ne peut jamais être complété par définition.
schot
1
@schot: Exactement. De plus, l'arithmétique du pointeur n'est définie que sur un pointeur vers un élément d'un objet tableau et seulement si le résultat de l'opération serait un pointeur vers un élément de ce même tableau ou un après le dernier élément de ce tableau. Si ces conditions ne sont pas remplies, il s'agit d'un comportement indéfini. (À partir de la norme C99 6.5.6.8)
Job
1
Apparemment, ce n'est pas le cas avec gcc 7.3.0. Le compilateur accepte p + 1024, où p est vide *. Et le résultat est le même que ((char *) p) + 1024
zzz777
@ zzz777 c'est une extension GCC, voir le lien dans la réponse la plus votée.
Ruslan le
19

transtypez-le en pointeur char et incrémentez votre pointeur en avant de x octets.

Gobblement ultime
la source
4
Pourquoi s'embêter? Il suffit de le convertir dans le bon type - qui devrait toujours être connu - et de l'incrémenter de 1. Le cast char, l'incrémentation de x, puis la réinterprétation de la nouvelle valeur comme un autre type est à la fois un comportement inutile et indéfini.
underscore_d
9
Si vous écrivez votre fonction de tri, qui selon man 3 qsortdevrait avoir le void qsort(void *base, size_t nmemb, size_t size, [snip]), alors vous n'avez aucun moyen de connaître le "bon type"
alisianoi
16

La norme C n'autorise pas l' arithmétique des pointeurs vides . Cependant, GNU C est autorisé en considérant la taille de void est 1.

Norme C11 §6.2.5

Paragraphe - 19

Le voidtype comprend un ensemble vide de valeurs; il s'agit d'un type d'objet incomplet qui ne peut pas être complété.

Le programme suivant fonctionne bien dans le compilateur GCC.

#include<stdio.h>

int main()
{
    int arr[2] = {1, 2};
    void *ptr = &arr;
    ptr = ptr + sizeof(int);
    printf("%d\n", *(int *)ptr);
    return 0;
}

Peut-être que d'autres compilateurs génèrent une erreur.

msc
la source
14

Vous ne pouvez pas faire d'arithmétique de pointeur sur les void *types, exactement pour cette raison!

Oliver Charlesworth
la source
8

Vous devez le convertir en un autre type de pointeur avant d'effectuer l'arithmétique du pointeur.

Jakob
la source
7

Les pointeurs vides peuvent pointer vers n'importe quel bloc de mémoire. Par conséquent, le compilateur ne sait pas combien d'octets incrémenter / décrémenter lorsque nous tentons d'arithmétique de pointeur sur un pointeur void. Par conséquent, les pointeurs vides doivent d'abord être transtypés vers un type connu avant de pouvoir être impliqués dans une arithmétique de pointeur.

void *p = malloc(sizeof(char)*10);
p++; //compiler does how many where to pint the pointer after this increment operation

char * c = (char *)p;
c++;  // compiler will increment the c by 1, since size of char is 1 byte.
tchrist
la source
-1

Le compilateur connaît par type cast. Étant donné un void *x:

  • x+1ajoute un octet à x, le pointeur passe à l'octetx+1
  • (int*)x+1ajoute des sizeof(int)octets, le pointeur passe à l'octetx + sizeof(int)
  • (float*)x+1addres sizeof(float)octets, etc.

Bien que le premier élément ne soit pas portable et soit contre le Galateo de C / C ++, il est néanmoins correct en langage C, ce qui signifie qu'il se compilera en quelque chose sur la plupart des compilateurs nécessitant éventuellement un drapeau approprié (comme -Wpointer-arith)

rytis
la source
2
Althought the first item is not portable and is against the Galateo of C/C++Vrai. it is nevertheless C-language-correctFaux. C'est une double pensée! L'arithmétique du pointeur void *est syntaxiquement illégale, ne doit pas se compiler et produit un comportement indéfini si c'est le cas. Si un programmeur imprudent peut le faire compiler en désactivant un avertissement, ce n'est pas une excuse.
underscore_d
@underscore_d: Je pense que certains compilateurs l'autorisaient en tant qu'extension, car c'est beaucoup plus pratique que d'avoir à faire un cast pour, unsigned char*par exemple, ajouter une sizeofvaleur à un pointeur.
supercat