... simplement décrémenter un pointeur en dehors de la plage allouée me semble très sommaire. Ce comportement est-il "autorisé" en C?
Permis? Oui. Bonne idée? Pas habituellement.
C est un raccourci pour le langage d'assemblage, et dans le langage d'assemblage, il n'y a pas de pointeurs, juste des adresses mémoire. Les pointeurs de C sont des adresses de mémoire qui ont un comportement secondaire d'incrémentation ou de décrémentation de la taille de ce qu'ils pointent lorsqu'ils sont soumis à l'arithmétique. Cela rend les éléments suivants très bien d'un point de vue syntaxique:
double *p = (double *)0xdeadbeef;
--p; // p == 0xdeadbee7, assuming sizeof(double) == 8.
double d = p[0];
Les tableaux ne sont pas vraiment une chose en C; ce ne sont que des pointeurs vers des plages de mémoire contiguës qui se comportent comme des tableaux. L' []
opérateur est un raccourci pour effectuer l'arithmétique des pointeurs et le déréférencement, donc a[x]
signifie en fait *(a + x)
.
Il existe des raisons valables de faire ce qui précède, comme certains périphériques d'E / S ayant un couple de double
s mappés dans 0xdeadbee7
et 0xdeadbeef
. Très peu de programmes devraient le faire.
Lorsque vous créez l'adresse de quelque chose, par exemple en utilisant l' &
opérateur ou en appelant malloc()
, vous souhaitez conserver intact le pointeur d'origine afin de savoir que ce qu'il pointe est en fait quelque chose de valide. La décrémentation du pointeur signifie qu'une partie du code erroné pourrait essayer de le déréférencer, d'obtenir des résultats erronés, d'altérer quelque chose ou, selon votre environnement, de commettre une violation de segmentation. Cela est particulièrement vrai avec malloc()
, car vous avez mis le fardeau sur celui qui appelle free()
de se rappeler de transmettre la valeur d'origine et non une version modifiée qui entraînera la perte de tout.
Si vous avez besoin de tableaux basés sur 1 en C, vous pouvez le faire en toute sécurité au détriment de l'allocation d'un élément supplémentaire qui ne sera jamais utilisé:
double *array_create(size_t size) {
// Wasting one element, so don't allow it to be full-sized
assert(size < SIZE_MAX);
return malloc((size+1) * sizeof(double));
}
inline double array_index(double *array, size_t index) {
assert(array != NULL);
assert(index >= 1); // This is a 1-based array
return array[index];
}
Notez que cela ne fait rien pour protéger contre le dépassement de la limite supérieure, mais c'est assez facile à gérer.
Addenda:
Quelques chapitres et versets du brouillon C99 (désolé, c'est tout ce que je peux lier):
Le §6.5.2.1.1 indique que la deuxième expression ("autre") utilisée avec l'opérateur d'indice est de type entier. -1
est un entier, ce qui rend p[-1]
valide et rend donc également le pointeur &(p[-1])
valide. Cela n'implique pas que l'accès à la mémoire à cet emplacement produirait un comportement défini, mais le pointeur est toujours un pointeur valide.
Le §6.5.2.2 dit que l'opérateur d'indice de tableau est évalué à l'équivalent de l'ajout du numéro d'élément au pointeur, p[-1]
est donc équivalent à *(p + (-1))
. Toujours valide, mais peut ne pas produire de comportement souhaitable.
Le §6.5.6.8 dit (c'est moi qui souligne):
Lorsqu'une expression de type entier est ajoutée ou soustraite d'un pointeur, le résultat a le type de l'opérande du pointeur.
... si l'expression P
pointe vers le i
-ème élément d'un objet tableau, les expressions (P)+N
(de manière équivalente N+(P)
) et (P)-N
(où N
a la valeur n
) pointent respectivement vers les éléments i+n
-th et
i−n
-th de l'objet tableau, à condition qu'elles existent .
Cela signifie que les résultats de l'arithmétique des pointeurs doivent pointer sur un élément d'un tableau. Il ne dit pas que l'arithmétique doit être faite en même temps. Donc:
double a[20];
// This points to element 9 of a; behavior is defined.
double d = a[-1 + 10];
double *p = a - 1; // This is just a pointer. No dereferencing.
double e = p[0]; // Does not point at any element of a; behavior is undefined.
double f = p[1]; // Points at element 0 of a; behavior is defined.
Dois-je recommander de faire les choses de cette façon? Non, et ma réponse explique pourquoi.