Ist es sicher, einen Zeiger außerhalb der Grenzen zu halten, ohne ihn zu dereferenzieren? [Duplikat]
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
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.
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
P
auf das i- te Element eines Array-Objekts zeigt, zeigen die Ausdrücke(P)+N
(äquivalentN+(P)
) und(P)-N
(wobeiN
der Wert n ist ) jeweils auf das i + n- te und das i-n- te Elemente des Array-Objekts, sofern sie vorhanden sind. Wenn der AusdruckP
auf das letzte Element eines Array-Objekts zeigt, zeigt der Ausdruck(P)+1
eins nach dem letzten Element des Array-Objekts, und wenn der AusdruckQ
eins nach dem letzten Element eines Array-Objekts zeigt, zeigt der Ausdruck(Q)-1
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.
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.
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 end
Punkte auf dem gleichen Element wie arr
wird die Bedingung falsch sein, aber nach , dass end
verringert 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 end
das letzte Mal nicht dekrementiert wird.