Ist es sicher, einen Zeiger außerhalb der Grenzen zu halten, ohne ihn zu dereferenzieren? [Duplikat]

Jan 10 2021

Ist es in C sicher, einen Zeiger für weitere Arithmetik außerhalb der Grenzen zu halten (ohne ihn zu dereferenzieren)?

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

Ich stelle mir einige Extremfälle vor, wenn die Adresse die erste oder die letzte Adresse ist ...

Antworten

9 MikeCAT Jan 10 2021 at 07:31

Das Bewegen des Zeigers auf ein Element nach dem letzten Element ist zulässig, das Weiterbewegen oder Bewegen vor dem ersten Element ist jedoch nicht zulässig.

Zitat aus N1570 6.5.6 Additive Operatoren (Punkt 8):

Wenn ein Ausdruck mit einem ganzzahligen Typ zu einem Zeiger hinzugefügt oder von diesem subtrahiert wird, hat das Ergebnis den Typ des Zeigeroperanden. Wenn der Zeigeroperand auf ein Element eines Array-Objekts zeigt und das Array groß genug ist, zeigt das Ergebnis auf ein Element, das vom ursprünglichen Element versetzt ist, sodass die Differenz der Indizes der resultierenden und ursprünglichen Array-Elemente dem ganzzahligen Ausdruck entspricht. Mit anderen Worten, wenn der Ausdruck P auf das i-te Element eines Array-Objekts zeigt, zeigen die Ausdrücke (P) + N (äquivalent N + (P)) und (P) -N (wobei N den Wert n hat) zu den i + n-ten bzw. i-n-ten Elementen des Array-Objekts, sofern sie existieren. Wenn der Ausdruck P auf das letzte Element eines Array-Objekts zeigt, zeigt der Ausdruck (P) +1 um eins nach dem letzten Element des Array-Objekts, und wenn der Ausdruck Q um eins nach dem letzten Element eines Array-Objekts zeigt, Der Ausdruck (Q) -1 zeigt auf das letzte Element des Array-Objekts. Wenn sowohl der Zeigeroperand als auch das Ergebnis auf Elemente desselben Arrayobjekts oder eines nach dem letzten Element des Arrayobjekts zeigen, darf die Auswertung keinen Überlauf erzeugen. Andernfalls ist das Verhalten undefiniert. Wenn das Ergebnis eins nach dem letzten Element des Array-Objekts zeigt, darf es nicht als Operand eines unären * Operators verwendet werden, der ausgewertet wird.

3 dbush Jan 10 2021 at 07:32

Ein Zeiger kann auf ein Element nach dem letzten Element des Arrays zeigen, und eine Zeigerarithmetik kann zwischen diesem Zeiger und einem Zeiger auf ein Element des Arrays durchgeführt werden.

Ein solcher Zeiger kann nicht dereferenziert werden, kann aber in der Zeigerarithmetik verwendet werden. Zum Beispiel ist Folgendes gültig:

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

Ein Zeiger darf nicht vor dem ersten Element zeigen.

Dies ist in Abschnitt 6.5.6p8 des C-Standards beschrieben :

Wenn ein Ausdruck mit einem ganzzahligen Typ zu einem Zeiger hinzugefügt oder von diesem subtrahiert wird, hat das Ergebnis den Typ des Zeigeroperanden. Wenn der Zeigeroperand auf ein Element eines Array-Objekts zeigt und das Array groß genug ist, zeigt das Ergebnis auf ein Element, das vom ursprünglichen Element versetzt ist, sodass die Differenz der Indizes der resultierenden und ursprünglichen Array-Elemente dem ganzzahligen Ausdruck entspricht. Mit anderen Worten, wenn der Ausdruck Pauf das i- te Element eines Array-Objekts zeigt, zeigen die Ausdrücke (P)+N(äquivalent N+(P)) und (P)-N(wobei Nder Wert n ist ) jeweils auf das i + n- te und das i-n- te Elemente des Array-Objekts, sofern sie vorhanden sind. Wenn der Ausdruck Pauf das letzte Element eines Array-Objekts zeigt, zeigt der Ausdruck (P)+1eins nach dem letzten Element des Array-Objekts, und wenn der Ausdruck Qeins nach dem letzten Element eines Array-Objekts zeigt, zeigt der Ausdruck (Q)-1auf das letzte Element des Array-Objekts. Wenn sowohl der Zeigeroperand als auch das Ergebnis auf Elemente desselben Arrayobjekts oder eines nach dem letzten Element des Arrayobjekts zeigen, darf die Auswertung keinen Überlauf erzeugen. Andernfalls ist das Verhalten undefiniert. Wenn das Ergebnis eins nach dem letzten Element des Array-Objekts zeigt, darf es nicht als Operand eines unären *Operators verwendet werden, der ausgewertet wird.

Beachten Sie, dass der fettgedruckte Teil angibt, dass ein Zeiger erstellt werden kann, der auf ein Element nach dem Ende des Arrays zeigt, und dass nichts auf einen Punkt vor dem Start des Arrays verweisen darf.

klutt Jan 10 2021 at 08:19

Wie andere bereits betont haben, dürfen Sie auf eine Vergangenheit hinweisen . Denken Sie jedoch daran, dass es NICHT erlaubt ist, ein Element vor dem ersten zu zeigen. Sie sollten also vorsichtig sein, wenn Sie Algorithmen schreiben, die Arrays rückwärts durchlaufen. Weil dieses Snippet ungültig ist:

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

Das liegt daran, wenn endPunkte auf dem gleichen Element wie arrwird die Bedingung falsch sein, aber nach , dass endverringert wird einmal mehr und wird zu einem Element Punkt vor dem Array, also nicht definiertes Verhalten aufruft.

Beachten Sie, dass der Code ansonsten einwandfrei funktioniert. Um den Fehler zu beheben, können Sie stattdessen Folgendes tun:

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
}

Der Code in der Schleife funktioniert genauso wie zuvor. Der einzige Unterschied besteht darin, dass enddas letzte Mal nicht dekrementiert wird.