... simplemente disminuir un puntero fuera del rango asignado me parece muy incompleto. ¿Es este comportamiento "permitido" en C?
¿Permitido? Si. ¿Buena idea? No Usualmente.
C es una abreviatura de lenguaje ensamblador, y en lenguaje ensamblador no hay punteros, solo direcciones de memoria. Los punteros de C son direcciones de memoria que tienen un comportamiento secundario de aumento o disminución por el tamaño de lo que apuntan cuando se someten a aritmética. Esto hace que lo siguiente esté bien desde una perspectiva de sintaxis:
double *p = (double *)0xdeadbeef;
--p; // p == 0xdeadbee7, assuming sizeof(double) == 8.
double d = p[0];
Las matrices no son realmente una cosa en C; son solo punteros a rangos contiguos de memoria que se comportan como matrices. El []
operador es una abreviatura para hacer aritmética de puntero y desreferenciar, por lo que en a[x]
realidad significa *(a + x)
.
Hay razones válidas para hacer lo anterior, como algunos dispositivos de E / S que tienen un par de double
s asignados a 0xdeadbee7
y 0xdeadbeef
. Muy pocos programas tendrían que hacer eso.
Cuando crea la dirección de algo, como mediante el uso del &
operador o la llamada malloc()
, desea mantener intacto el puntero original para que sepa que lo que apunta en realidad es algo válido. Disminuir el puntero significa que un poco de código errante podría intentar desreferenciarlo, obteniendo resultados erróneos, golpeando algo o, dependiendo de su entorno, cometiendo una violación de segmentación. Esto es especialmente cierto con malloc()
, porque usted ha puesto la carga sobre quien llama free()
para recordar pasar el valor original y no alguna versión alterada que hará que se suelte todo.
Si necesita matrices basadas en 1 en C, puede hacerlo de forma segura a expensas de asignar un elemento adicional que nunca se utilizará:
double *array_create(size_t size) {
// Wasting one element, so don't allow it to be full-sized
assert(size < SIZE_MAX);
return malloc((size+1) * sizeof(double));
}
inline double array_index(double *array, size_t index) {
assert(array != NULL);
assert(index >= 1); // This is a 1-based array
return array[index];
}
Tenga en cuenta que esto no hace nada para proteger contra exceder el límite superior, pero eso es lo suficientemente fácil de manejar.
Apéndice:
Algunos capítulos y versículos del borrador del C99 (lo siento, es todo lo que puedo vincular):
§6.5.2.1.1 dice que la segunda expresión ("otra") utilizada con el operador de subíndice es de tipo entero. -1
es un número entero, y eso lo hace p[-1]
válido y, por lo tanto, también hace que el puntero sea &(p[-1])
válido. Esto no implica que acceder a la memoria en esa ubicación produzca un comportamiento definido, pero el puntero sigue siendo un puntero válido.
§6.5.2.2 dice que el operador de subíndice de matriz evalúa el equivalente de agregar el número de elemento al puntero, por p[-1]
lo tanto, es equivalente a *(p + (-1))
. Sigue siendo válido, pero puede no producir un comportamiento deseable.
§6.5.6.8 dice (énfasis mío):
Cuando una expresión que tiene un tipo entero se agrega o resta de un puntero, el resultado tiene el tipo del operando del puntero.
... si la expresión P
apunta al i
elemento -th de un objeto de matriz, las expresiones (P)+N
(equivalentemente N+(P)
) y (P)-N
(donde N
tiene el valor n
) apuntan, respectivamente, a los elementos i+n
-th y
i−n
-th del objeto de matriz, siempre que existan .
Esto significa que los resultados de la aritmética del puntero tienen que apuntar a un elemento en una matriz. No dice que la aritmética debe hacerse de una vez. Por lo tanto:
double a[20];
// This points to element 9 of a; behavior is defined.
double d = a[-1 + 10];
double *p = a - 1; // This is just a pointer. No dereferencing.
double e = p[0]; // Does not point at any element of a; behavior is undefined.
double f = p[1]; // Points at element 0 of a; behavior is defined.
¿Recomiendo hacer las cosas de esta manera? No lo hago, y mi respuesta explica por qué.