É seguro manter um ponteiro fora dos limites sem desreferenciá-lo? [duplicado]

Jan 10 2021

É 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

9 MikeCAT Jan 10 2021 at 07:31

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.

3 dbush Jan 10 2021 at 07:32

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 Paponta para o i -ésimo elemento de um objeto de matriz, as expressões (P)+N(equivalentemente, N+(P)) e (P)-N(onde Ntem 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ão Papontar para o último elemento de um objeto de matriz, a expressão (P)+1aponta um após o último elemento do objeto de matriz, e se a expressão Qaponta para o último elemento de um objeto de matriz, a expressão (Q)-1aponta 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.

klutt Jan 10 2021 at 08:19

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 endapontar para o mesmo elemento que arr, a condição será falsa, mas depois disso, endserá 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 endnão será diminuído da última vez.