É seguro manter um ponteiro fora dos limites sem desreferenciá-lo? [duplicado]
É seguro em C manter um ponteiro fora dos limites (sem desreferenciá-lo) para aritmética posterior?
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;
}
}
Imagino alguns casos extremos, se o endereço for o primeiro de memória ou o último ...
Respostas
Mover o ponteiro para um elemento após o último elemento é permitido, mas mover mais ou antes do primeiro elemento não é permitido.
Citação de N1570 6.5.6 Operadores aditivos (ponto 8):
Quando uma expressão que possui o tipo inteiro é adicionada ou subtraída de um ponteiro, o resultado tem o tipo do operando ponteiro. Se o operando de ponteiro apontar para um elemento de um objeto de matriz, e a matriz for grande o suficiente, o resultado aponta para um elemento deslocado do elemento original de forma que a diferença dos subscritos dos elementos resultantes e originais da matriz seja igual à expressão inteira. Em outras palavras, se a expressão P aponta para o i-ésimo elemento de um objeto de matriz, as expressões (P) + N (equivalentemente, N + (P)) e (P) -N (onde N tem o valor n) apontam para, respectivamente, o i + n-ésimo e i-n-ésimo elementos do objeto de matriz, desde que existam. Além disso, se a expressão P aponta para o último elemento de um objeto de matriz, a expressão (P) +1 aponta um após o último elemento do objeto de matriz, e se a expressão Q aponta um após o último elemento de um objeto de matriz, a expressão (Q) -1 aponta para o último elemento do objeto de matriz. Se tanto o operando de ponteiro quanto o resultado apontam para elementos do mesmo objeto de array, ou um após o último elemento do objeto de array, a avaliação não deve produzir um estouro; caso contrário, o comportamento é indefinido. Se o resultado apontar um após o último elemento do objeto de matriz, ele não deve ser usado como o operando de um operador unário * que é avaliado.
Um ponteiro pode apontar para um elemento após o último elemento da matriz, e a aritmética de ponteiro pode ser feita entre esse ponteiro e um ponteiro para um elemento da matriz.
Esse ponteiro não pode ser referenciado, mas pode ser usado na aritmética de ponteiro. Por exemplo, o seguinte é válido:
char arr[10];
char *p1, *p2;
p1 = arr + 10;
p2 = arr + 5;
int diff = p1 - p2;
printf("diff=%d\n", diff); // prints 5
Um ponteiro não pode apontar antes do primeiro elemento.
Isso é explicado na seção 6.5.6p8 do padrão C :
Quando uma expressão que possui o tipo inteiro é adicionada ou subtraída de um ponteiro, o resultado tem o tipo do operando ponteiro. Se o operando de ponteiro apontar para um elemento de um objeto de matriz, e a matriz for grande o suficiente, o resultado aponta para um elemento deslocado do elemento original de forma que a diferença dos subscritos dos elementos resultantes e originais da matriz seja igual à expressão inteira. Em outras palavras, se a expressão
P
aponta para o i -ésimo elemento de um objeto de matriz, as expressões(P)+N
(equivalentemente,N+(P)
) e(P)-N
(ondeN
tem o valor n ) apontam para, respectivamente, i + n -ésimo e i-n- ésimo elementos do objeto array, desde que existam. Além disso, se a expressãoP
apontar para o último elemento de um objeto de matriz, a expressão(P)+1
aponta um após o último elemento do objeto de matriz, e se a expressãoQ
aponta para o último elemento de um objeto de matriz, a expressão(Q)-1
aponta para o último elemento do objeto de matriz. Se tanto o operando de ponteiro quanto o resultado apontam para elementos do mesmo objeto de array, ou um após o último elemento do objeto de array, a avaliação não deve produzir um estouro; caso contrário, o comportamento é indefinido. Se o resultado apontar um após o último elemento do objeto de matriz, ele não deve ser usado como o operando de um*
operador unário que é avaliado.
Observe a parte em negrito que afirma que um ponteiro pode ser criado para apontar para um elemento além do final da matriz, e não há nada que permita apontar para qualquer ponto antes do início da matriz.
Como outros apontaram, você tem permissão para apontar um passado . Mas lembre-se de que NÃO é permitido apontar um elemento antes do primeiro. Portanto, você pode querer ter cuidado ao escrever algoritmos que percorram os arrays de trás para frente. Porque este snippet é inválido:
void foo(int *arr, int *end) {
while(end-- != arr) { // Ouch, bad idea...
// Code
}
// Here, end has the value arr[-1]
}
Isso porque, quando end
apontar para o mesmo elemento que arr
, a condição será falsa, mas depois disso, end
será decrementada mais uma vez e apontará para um elemento antes do array, invocando assim um comportamento indefinido.
Observe que, além disso, o código funciona bem. Para corrigir o bug, você pode fazer o seguinte:
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
}
O código no loop funcionará exatamente como antes. A única diferença é que end
não será diminuído da última vez.