Czy bezpieczne jest trzymanie wskaźnika poza zakresem bez wyłuskiwania go? [duplikować]

Jan 10 2021

Czy w C jest bezpiecznie trzymać wskaźnik poza zakresem (bez wyłuskiwania go) dla dalszych działań arytmetycznych?

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

Wyobrażam sobie ekstremalne przypadki, jeśli adres jest pierwszym ze wspomnień, czy ostatnim ...

Odpowiedzi

9 MikeCAT Jan 10 2021 at 07:31

Przesuwanie wskaźnika do jednego elementu poza ostatni element jest dozwolone, ale przesuwanie dalej lub przesuwanie przed pierwszym elementem jest niedozwolone.

Cytat z N1570 6.5.6 Operatory addytywne (punkt 8):

Gdy wyrażenie, które ma typ całkowity, jest dodawane lub odejmowane od wskaźnika, wynik ma typ operandu wskaźnika. Jeśli operand wskaźnika wskazuje na element obiektu tablicy, a tablica jest wystarczająco duża, wynik wskazuje na element przesunięty w stosunku do oryginalnego elementu w taki sposób, że różnica między indeksami wynikowymi i oryginalnymi elementami tablicy jest równa wyrażeniu całkowitemu. Innymi słowy, jeśli wyrażenie P wskazuje na i-ty element obiektu tablicy, wyrażenia (P) + N (równoważnie, N + (P)) i (P) -N (gdzie N ma wartość n) punkt odpowiednio do i + n-tego i i-n-tego elementu obiektu tablicy, o ile istnieją. Co więcej, jeśli wyrażenie P wskazuje na ostatni element obiektu tablicy, wyrażenie (P) +1 wskazuje jeden za ostatni element obiektu tablicy, a jeśli wyrażenie Q wskazuje jeden za ostatni element obiektu tablicy, wyrażenie (Q) -1 wskazuje na ostatni element obiektu tablicy. Jeśli zarówno operand wskaźnika, jak i wynik wskazują na elementy tego samego obiektu tablicy lub jeden za ostatnim elementem obiektu tablicy, ocena nie spowoduje przepełnienia; w przeciwnym razie zachowanie jest niezdefiniowane. Jeśli wynik wskazuje jeden za ostatnim elementem obiektu tablicy, nie powinien być używany jako argument operacji jednoargumentowego operatora *, który jest oceniany.

3 dbush Jan 10 2021 at 07:32

Wskaźnik może wskazywać na jeden element za ostatnim elementem tablicy, a arytmetyka wskaźnika może być wykonywana między tym wskaźnikiem a wskaźnikiem do elementu tablicy.

Takiego wskaźnika nie można wyłuskać, ale można go użyć w arytmetyce wskaźników. Na przykład obowiązuje:

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

Wskaźnik nie może wskazywać przed pierwszym elementem.

Jest to opisane w sekcji 6.5.6p8 standardu C :

Gdy wyrażenie, które ma typ całkowity, jest dodawane lub odejmowane od wskaźnika, wynik ma typ operandu wskaźnika. Jeśli operand wskaźnika wskazuje na element obiektu tablicy, a tablica jest wystarczająco duża, wynik wskazuje na element przesunięty w stosunku do oryginalnego elementu w taki sposób, że różnica indeksów wynikowych i oryginalnych elementów tablicy jest równa wyrażeniu całkowitemu. Innymi słowy, jeśli wyrażenie Pwskazuje na i- ty element obiektu tablicy, wyrażenia (P)+N(równoważnie, N+(P)) i (P)-N(gdzie Nma wartość n ) wskazują, odpowiednio, i + n -ty i i-n -ty elementy obiektu tablicy, o ile istnieją. Ponadto, jeśli wyrażenie Pwskazuje na ostatni element obiektu tablicy, wyrażenie (P)+1wskazuje o jeden za ostatnim elementem obiektu tablicy, a jeśli wyrażenie Qwskazuje o jeden za ostatnim elementem obiektu tablicy, wyrażenie (Q)-1wskazuje na ostatni element obiektu tablicy. Jeśli zarówno operand wskaźnika, jak i wynik wskazują na elementy tego samego obiektu tablicy lub jeden za ostatnim elementem obiektu tablicy, ocena nie spowoduje przepełnienia; w przeciwnym razie zachowanie jest niezdefiniowane. Jeśli wynik wskazuje jeden za ostatnim elementem obiektu tablicy, nie może być używany jako argument operacji jednoargumentowego *operatora, który jest oceniany.

Zauważ, że pogrubiona część, która stwierdza, że ​​można utworzyć wskaźnik wskazujący jeden element poza koniec tablicy, i nie ma nic, co pozwala wskazać dowolny punkt przed początkiem tablicy.

klutt Jan 10 2021 at 08:19

Jak zauważyli inni, możesz wskazać jedną przeszłość . Pamiętaj jednak, że NIE wolno wskazać jednego elementu przed pierwszym. Możesz więc zachować ostrożność, pisząc algorytmy, które przechodzą przez tablice wstecz. Ponieważ ten fragment jest nieprawidłowy:

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

Dzieje się tak, ponieważ gdy endwskazuje ten sam element co arr, warunek będzie fałszywy, ale po tym endjest ponownie zmniejszany i wskaże jeden element przed tablicą, wywołując niezdefiniowane zachowanie.

Zwróć uwagę, że poza tym kod działa dobrze. Aby naprawić błąd, możesz to zrobić zamiast tego:

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
}

Kod w pętli będzie działał dokładnie tak, jak poprzednio. Jedyna różnica polega na tym end, że ostatnim razem nie zostanie zmniejszona.