operaciones - punteros c++
¿Está bien definido usar un puntero que apunta a un malloc pasado? (4)
¿Está bien definido usar un puntero que apunta a un malloc pasado?
Está bien definido si p
está apuntando a una pasada la memoria asignada y no está desreferenciada.
n1570 - §6.5.6 (p8):
[...] Si el resultado apunta uno más allá del último elemento del objeto de matriz, no se utilizará como el operando de un operador unario
*
que se evalúa.
Restar dos punteros solo son válidos cuando apuntan a elementos del mismo objeto de matriz o uno más allá del último elemento del objeto de matriz, de lo contrario, resultará en un comportamiento indefinido.
(p9) :
Cuando se restan dos punteros, ambos apuntarán a elementos del mismo objeto de matriz, o uno más allá del último elemento del objeto de matriz [...]
Las citas anteriores son válidas para la memoria asignada tanto de forma dinámica como estática.
int a[5];
ptrdiff_t diff = &a[5] - &a[0]; // Well-defined
int *d = malloc(5 * sizeof(*d));
assert(d != NULL, "Memory allocation failed");
diff = &d[5] - &d[0]; // Well-defined
Otra razón por la que esto es válido para la memoria asignada dinámicamente, como señaló Jonathan Leffler en un comment es:
§7.22.3 (p1) :
El orden y la continuidad del almacenamiento asignado por las llamadas sucesivas a las
aligned_alloc
,calloc
,malloc
yrealloc
no están especificados. El puntero que se devuelve si la asignación es exitosa se alinea adecuadamente para que pueda ser asignado a un puntero a cualquier tipo de objeto con un requisito de alineación fundamental y luego se use para acceder a dicho objeto o una matriz de tales objetos en el espacio asignado (hasta el el espacio está desasignado explícitamente).
El puntero devuelto por malloc
en el fragmento anterior se asigna a d
y la memoria asignada es una matriz de 5 objetos int
.
En C, está perfectamente bien hacer un puntero que apunte a uno más allá del último elemento de una matriz y usarlo en aritmética de punteros, siempre y cuando no lo elimines:
int a[5], *p = a+5, diff = p-a; // Well-defined
Sin embargo, estos son UBs:
p = a+6;
int b = *(a+5), diff = p-a; // Dereferencing and pointer arithmetic
Ahora tengo una pregunta: ¿Se aplica esto a la memoria asignada dinámicamente? Supongamos que solo estoy usando un puntero que apunta a una aritmética de punteros en el pasado, sin eliminarla, y malloc()
tiene éxito.
int *a = malloc(5 * sizeof(*a));
assert(a != NULL, "Memory allocation failed");
// Question:
int *p = a+5;
int diff = p-a; // Use in pointer arithmetic?
¿Está bien definido usar un puntero que apunta a un malloc pasado?
Sí, todavía existe un caso de esquina donde esto no está bien definido:
void foo(size_t n) {
int *a = malloc(n * sizeof *a);
assert(a != NULL || n == 0, "Memory allocation failed");
int *p = a+n;
intptr_t diff = p-a;
...
}
Funciones de administración de memoria ... Si el tamaño del espacio solicitado es cero, el comportamiento está definido por la implementación: se devuelve un puntero nulo o el comportamiento es como si el tamaño fuera un valor distinto de cero, excepto que el puntero devuelto no debe ser utilizado para acceder a un objeto. C11dr §7.22.3 1
foo(0)
-> malloc(0)
puede devolver un valor NULL
o non-NULL
. En la primera implementación, un retorno de NULL
no es un "error de asignación de memoria". Esto significa que el código está intentando int *p = NULL + 0;
con int *p = a+n;
lo que no cumple con las garantías sobre el puntero matemático, o al menos pone en tela de juicio tal código.
Beneficios de código portátil al evitar asignaciones de tamaño 0.
void bar(size_t n) {
intptr_t diff;
int *a;
int *p;
if (n > 0) {
a = malloc(n * sizeof *a);
assert(a != NULL, "Memory allocation failed");
p = a+n;
diff = p-a;
} else {
a = p = NULL;
diff = 0;
}
...
}
El borrador n4296 para C11 es explícito en el sentido de que apuntar uno más allá de una matriz está perfectamente definido: 6.5.6 Operadores de lenguaje / expresiones / aditivos:
§ 8 Cuando una expresión que tiene un tipo entero se agrega o se resta de un puntero, el resultado tiene el tipo del operando puntero. ... 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 uno más allá del último elemento de una objeto de matriz, la expresión (Q) -1 apunta al último elemento del objeto de matriz ... Si el resultado apunta a uno más allá del último elemento del objeto de matriz, no se usará como el operando de un operador unario * que es evaluado
Como el tipo de memoria nunca se especifica en la subcláusula, se aplica a cualquier tipo de memoria, incluida una asignada.
Eso significa claramente que después de:
int *a = malloc(5 * sizeof(*a));
assert(a != NULL, "Memory allocation failed");
ambos
int *p = a+5;
int diff = p-a;
están perfectamente definidas y, como se aplican las reglas aritméticas de punteros habituales, diff
recibirá el valor 5
.
Sí, las mismas reglas se aplican a las variables con duración de almacenamiento dinámico y automático. Incluso se aplica a una solicitud malloc
para un solo elemento (un escalar es equivalente a una matriz de un elemento a este respecto).
La aritmética de punteros solo es válida dentro de matrices, incluida una pasada el final de una matriz.
En la eliminación de referencias, es importante tener en cuenta una consideración: con respecto a la inicialización int a[5] = {0};
, el compilador no debe intentar anular la referencia a[5]
en la expresión int* p = &a[5]
; debe compilar esto como int* p = a + 5;
De nuevo, lo mismo se aplica al almacenamiento dinámico.