È sicuro mantenere un puntatore fuori dai limiti senza dereferenziarlo? [duplicare]

Jan 10 2021

È sicuro in C mantenere un puntatore fuori dai limiti (senza dereferenziarlo) per ulteriori operazioni aritmetiche?

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;
    }
}

Immagino alcuni casi estremi, se l'indirizzo è il primo della memoria o l'ultimo ...

Risposte

9 MikeCAT Jan 10 2021 at 07:31

È consentito spostare il puntatore su un elemento dopo l'ultimo elemento, ma non è consentito spostarsi ulteriormente o spostarsi prima del primo elemento.

Citazione da N1570 6.5.6 Operatori additivi (punto 8):

Quando un'espressione con un tipo intero viene aggiunta o sottratta da un puntatore, il risultato ha il tipo dell'operando del puntatore. Se l'operando del puntatore punta a un elemento di un oggetto array e l'array è abbastanza grande, il risultato punta a un elemento spostato dall'elemento originale in modo tale che la differenza tra i pedici degli elementi dell'array risultante e originale sia uguale all'espressione intera. In altre parole, se l'espressione P punta all'i-esimo elemento di un oggetto array, le espressioni (P) + N (equivalentemente, N + (P)) e (P) -N (dove N ha il valore n) indicano rispettivamente agli elementi i + n-esimo e i-n-esimo dell'oggetto array, a condizione che esistano. Inoltre, se l'espressione P punta all'ultimo elemento di un oggetto array, l'espressione (P) +1 punta uno oltre l'ultimo elemento dell'oggetto array, e se l'espressione Q punta uno oltre l'ultimo elemento di un oggetto array, l'espressione (Q) -1 punta all'ultimo elemento dell'oggetto array. Se sia l'operando puntatore che il risultato puntano a elementi dello stesso oggetto array, o uno dopo l'ultimo elemento dell'oggetto array, la valutazione non deve produrre un overflow; in caso contrario, il comportamento è indefinito. Se il risultato punta uno oltre l'ultimo elemento dell'oggetto array, non deve essere utilizzato come operando di un operatore unario * che viene valutato.

3 dbush Jan 10 2021 at 07:32

Un puntatore può puntare a un elemento dopo l'ultimo elemento della matrice e l'aritmetica del puntatore può essere eseguita tra quel puntatore e un puntatore a un elemento della matrice.

Un tale puntatore non può essere dereferenziato, ma può essere utilizzato nell'aritmetica dei puntatori. Ad esempio, è valido quanto segue:

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

Un puntatore non può puntare prima del primo elemento.

Questo è spiegato nella sezione 6.5.6p8 dello standard C :

Quando un'espressione con un tipo intero viene aggiunta o sottratta da un puntatore, il risultato ha il tipo dell'operando del puntatore. Se l'operando del puntatore punta a un elemento di un oggetto array e l'array è abbastanza grande, il risultato punta a un elemento spostato dall'elemento originale in modo tale che la differenza tra i pedici degli elementi dell'array risultante e originale sia uguale all'espressione intera. In altre parole, se l'espressione Ppunta all'i -esimo elemento di un oggetto array, le espressioni (P)+N(equivalentemente, N+(P)) e (P)-N(dove Nha il valore n ) puntano rispettivamente a i + n -esimo e i-n- esimo elementi dell'oggetto array, a condizione che esistano. Inoltre, se l'espressione Ppunta all'ultimo elemento di un oggetto array, l'espressione (P)+1punta uno oltre l'ultimo elemento dell'oggetto array e se l'espressione Qpunta uno oltre l'ultimo elemento di un oggetto array, l'espressione (Q)-1punta all'ultimo elemento dell'oggetto array. Se sia l'operando puntatore che il risultato puntano a elementi dello stesso oggetto array, o uno dopo l'ultimo elemento dell'oggetto array, la valutazione non deve produrre un overflow; in caso contrario, il comportamento è indefinito. Se il risultato punta uno oltre l'ultimo elemento dell'oggetto matrice, non deve essere utilizzato come operando di un *operatore unario che viene valutato.

Si noti che la parte in grassetto che afferma che un puntatore può essere creato per puntare a un elemento oltre la fine della matrice e non c'è nulla che consenta di puntare a qualsiasi punto prima dell'inizio della matrice.

klutt Jan 10 2021 at 08:19

Come altri hanno sottolineato, ti è permesso indicare un passato . Ma ricorda che NON è consentito puntare un elemento prima del primo. Quindi potresti voler stare attento se scrivi algoritmi che attraversano gli array all'indietro. Perché questo snippet non è valido:

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

Questo perché, quando endpunti alla stesso elemento arr, la condizione sarà falsa, ma dopo che, endviene decrementato di nuovo e si dirigerà verso un elemento prima matrice, invocando così comportamento indefinito.

Tieni presente che, a parte questo, il codice funziona bene. Per correggere il bug, puoi farlo invece:

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
}

Il codice nel ciclo funzionerà esattamente come prima. L'unica differenza è che endl'ultima volta non verrà diminuito.