È sicuro mantenere un puntatore fuori dai limiti senza dereferenziarlo? [duplicare]
È 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
È 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.
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
P
punta all'i -esimo elemento di un oggetto array, le espressioni(P)+N
(equivalentemente,N+(P)
) e(P)-N
(doveN
ha il valore n ) puntano rispettivamente a i + n -esimo e i-n- esimo elementi dell'oggetto array, a condizione che esistano. Inoltre, se l'espressioneP
punta all'ultimo elemento di un oggetto array, l'espressione(P)+1
punta uno oltre l'ultimo elemento dell'oggetto array e se l'espressioneQ
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 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.
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 end
punti alla stesso elemento arr
, la condizione sarà falsa, ma dopo che, end
viene 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 end
l'ultima volta non verrà diminuito.