¿Es seguro mantener un puntero fuera de límites sin desreferenciarlo? [duplicar]
¿Es seguro en C mantener un puntero fuera de los límites (sin desreferenciarlo) para más aritmética?
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 algunos casos extremos, si la dirección es la primera de la memoria o la última ...
Respuestas
Se permite mover el puntero a un elemento más allá del último elemento, pero no se permite mover más o moverse antes del primer elemento.
Cita de N1570 6.5.6 Operadores aditivos (punto 8):
Cuando una expresión que tiene un tipo entero se suma o se resta de un puntero, el resultado tiene el tipo del operando del puntero. Si el operando de puntero apunta a un elemento de un objeto de matriz, y la matriz es lo suficientemente grande, el resultado apunta a un elemento desplazado del elemento original de modo que la diferencia de los subíndices de los elementos de matriz resultante y original sea igual a la expresión entera. En otras palabras, si la expresión P apunta al i-ésimo elemento de un objeto de matriz, las expresiones (P) + N (equivalentemente, N + (P)) y (P) -N (donde N tiene el valor n) apuntan a, respectivamente, los elementos i + n-ésimo e i-n-ésimo del objeto de matriz, siempre que existan. Además, si la expresión P apunta al último elemento de un objeto de matriz, la expresión (P) +1 apunta uno más allá del último elemento del objeto de matriz, y si la expresión Q apunta a uno más allá del último elemento de un objeto de matriz, la expresión (Q) -1 apunta al último elemento del objeto de matriz. Si tanto el operando del puntero como el resultado apuntan a elementos del mismo objeto de matriz, o uno más allá del último elemento del objeto de matriz, la evaluación no producirá un desbordamiento; de lo contrario, el comportamiento no está definido. Si el resultado apunta uno más allá del último elemento del objeto de matriz, no se utilizará como operando de un operador unario * que se evalúe.
Un puntero puede apuntar a un elemento más allá del último elemento de la matriz, y se puede hacer aritmética de puntero entre ese puntero y un puntero a un elemento de la matriz.
No se puede desreferenciar un puntero de este tipo, pero se puede utilizar en aritmética de punteros. Por ejemplo, lo siguiente es 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
Un puntero no puede apuntar antes del primer elemento.
Esto se detalla en la sección 6.5.6p8 del estándar C :
Cuando una expresión que tiene un tipo entero se suma o se resta de un puntero, el resultado tiene el tipo del operando del puntero. Si el operando de puntero apunta a un elemento de un objeto de matriz, y la matriz es lo suficientemente grande, el resultado apunta a un elemento desplazado del elemento original de modo que la diferencia de los subíndices de los elementos de matriz resultante y original sea igual a la expresión entera. En otras palabras, si la expresión
P
apunta al i -ésimo elemento de un objeto de matriz, las expresiones(P)+N
(de forma equivalente,N+(P)
) y(P)-N
(dondeN
tiene el valor n ) apuntan a, respectivamente, i + n -ésimo y i − n- ésimo elementos del objeto de matriz, siempre que existan. Además, si la expresiónP
apunta al último elemento de un objeto de matriz, la expresión(P)+1
apunta uno más allá del último elemento del objeto de matriz, y si la expresiónQ
apunta uno más allá del último elemento de un objeto de matriz, la expresión(Q)-1
apunta al último elemento del objeto de matriz. Si tanto el operando del puntero como el resultado apuntan a elementos del mismo objeto de matriz, o uno más allá del último elemento del objeto de matriz, la evaluación no producirá un desbordamiento; de lo contrario, el comportamiento no está definido. Si el resultado apunta uno más allá del último elemento del objeto de matriz, no se utilizará como operando de un*
operador unario que se evalúa.
Tenga en cuenta la parte en negrita que indica que se puede crear un puntero para apuntar a un elemento más allá del final de la matriz, y no hay nada que permita apuntar a ningún punto antes del inicio de la matriz.
Como han señalado otros, se le permite señalar un pasado . Pero recuerde que NO está permitido apuntar un elemento antes del primero. Por lo tanto, es posible que desee tener cuidado si escribe algoritmos que atraviesan matrices al revés. Porque este fragmento no es válido:
void foo(int *arr, int *end) {
while(end-- != arr) { // Ouch, bad idea...
// Code
}
// Here, end has the value arr[-1]
}
Eso es porque, cuando end
apunta al mismo elemento que arr
, la condición será falsa, pero después de eso, end
se decrementará una vez más y apuntará a un elemento antes de la matriz, invocando así un comportamiento indefinido.
Tenga en cuenta que, aparte de eso, el código funciona bien. Para corregir el error, puede hacer esto en su lugar:
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
}
El código en el ciclo funcionará exactamente como antes. La única diferencia es que end
no se decrementará la última vez.