Est-il sûr de garder un pointeur hors des limites sans le déréférencer? [dupliquer]

Jan 10 2021

Est-il sûr en C de garder un pointeur hors des limites (sans le déréférencer) pour plus d'arithmétique?

void f(int *array)
{
    int *i = array - 1;  // OOB

    while(...) {
        ++i;
        ...
    }
}

void g(int *array, int *end /* past-the-end pointer: OOB */)
{
    while(array != end) {
        ...
        ++array;
    }
}

J'imagine quelques cas extrêmes, si l'adresse est la première de mémoire ou la dernière ...

Réponses

9 MikeCAT Jan 10 2021 at 07:31

Le déplacement du pointeur vers un élément au-delà du dernier élément est autorisé, mais le déplacement plus loin ou le déplacement avant le premier élément n'est pas autorisé.

Citation de N1570 6.5.6 Opérateurs additifs (point 8):

Lorsqu'une expression de type entier est ajoutée ou soustraite à un pointeur, le résultat a le type de l'opérande du pointeur. Si l'opérande de pointeur pointe vers un élément d'un objet de tableau et que le tableau est suffisamment grand, le résultat pointe vers un décalage d'élément par rapport à l'élément d'origine de telle sorte que la différence des indices des éléments de tableau résultant et d'origine égale l'expression entière. En d'autres termes, 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 aux, respectivement, les i + n-ième et i-n-ième éléments de l'objet tableau, à condition qu'ils existent. De plus, si l'expression P pointe vers le dernier élément d'un objet tableau, l'expression (P) +1 pointe un après le dernier élément de l'objet tableau, et si l'expression Q pointe un après le dernier élément d'un objet tableau, l'expression (Q) -1 pointe vers le dernier élément de l'objet tableau. Si l'opérande du pointeur et le résultat pointent tous deux vers des éléments du même objet tableau, ou un après le dernier élément de l'objet tableau, l'évaluation ne produira pas de débordement; sinon, le comportement n'est pas défini. Si le résultat pointe un après le dernier élément de l'objet tableau, il ne doit pas être utilisé comme opérande d'un opérateur unaire * évalué.

3 dbush Jan 10 2021 at 07:32

Un pointeur peut pointer vers un élément après le dernier élément du tableau, et l'arithmétique du pointeur peut être effectuée entre ce pointeur et un pointeur vers un élément du tableau.

Un tel pointeur ne peut pas être déréférencé, mais il peut être utilisé dans l'arithmétique du pointeur. Par exemple, ce qui suit est valide:

char arr[10];
char *p1, *p2;
p1 = arr + 10;
p2 = arr + 5;
int diff = p1 - p2;
printf("diff=%d\n", diff);   // prints 5

Un pointeur peut ne pas pointer avant le premier élément.

Ceci est précisé dans la section 6.5.6p8 de la norme C :

Lorsqu'une expression de type entier est ajoutée ou soustraite à un pointeur, le résultat a le type de l'opérande du pointeur. Si l'opérande de pointeur pointe vers un élément d'un objet de tableau et que le tableau est suffisamment grand, le résultat pointe vers un décalage d'élément par rapport à l'élément d'origine de telle sorte que la différence des indices des éléments de tableau résultant et d'origine égale l'expression entière. En d'autres termes, si l'expression Ppointe vers le i -ème élément d'un objet tableau, les expressions (P)+N(de manière équivalente, N+(P)) et (P)-N(où Na la valeur n ) pointent respectivement vers le i + n -th et i-n -th éléments de l'objet tableau, à condition qu'ils existent. De plus, si l'expression Ppointe vers le dernier élément d'un objet tableau, l'expression (P)+1pointe un après le dernier élément de l'objet tableau, et si l'expression Qpointe un après le dernier élément d'un objet tableau, l'expression (Q)-1pointe vers le dernier élément de l'objet tableau. Si l'opérande du pointeur et le résultat pointent tous deux vers des éléments du même objet tableau, ou un après le dernier élément de l'objet tableau, l'évaluation ne produira pas de débordement; sinon, le comportement n'est pas défini. Si le résultat pointe un après le dernier élément de l'objet tableau, il ne doit pas être utilisé comme opérande d'un *opérateur unaire évalué.

Notez la partie en gras qui indique qu'un pointeur peut être créé pour pointer vers un élément au-delà de la fin du tableau, et rien ne permet de pointer vers un point quelconque avant le début du tableau.

klutt Jan 10 2021 at 08:19

Comme d'autres l'ont souligné, vous êtes autorisé à pointer un passé . Mais rappelez-vous qu'il n'est PAS permis de pointer un élément avant le premier. Vous voudrez peut-être faire attention si vous écrivez des algorithmes qui parcourent les tableaux à l'envers. Parce que cet extrait de code n'est pas valide:

void foo(int *arr, int *end) {
    while(end-- != arr) { // Ouch, bad idea...
        // Code
    }
    // Here, end has the value arr[-1]
}

En effet, lorsque endpointe sur le même élément que arr, la condition sera fausse, mais après cela, elle endest décrémentée une fois de plus et pointera vers un élément avant le tableau, invoquant ainsi un comportement indéfini.

Notez qu'en dehors de cela, le code fonctionne correctement. Pour corriger le bogue, vous pouvez le faire à la place:

void foo(int *arr, int *end) {
    while(end != arr) { 
        end--; // Move end-- to inside the loop, in the very beginning

        // Code
    }

    // And here, end is equal to arr, which is perfectly fine
}

Le code de la boucle fonctionnera exactement comme avant. La seule différence est qu'elle endne sera pas décrémentée la dernière fois.