En C, ¿es "legal" asignar menos memoria a un puntero a matriz si solo accedemos a elementos que se encuentran dentro de la memoria asignada? ¿O esto invoca un comportamiento indefinido?
int (*foo)[ 10 ]; //Pointer to array of 10 ints foo = malloc( sizeof( int ) * 5 ); //Under-allocation! //Only enough memory for 5 ints //Now we only ever access (*foo)[ 0 - 4 ]Si esto, en sí mismo, no es un comportamiento indefinido, ¿entonces el acceso a otro objeto no relacionado cuya dirección de memoria se encuentra dentro del espacio de direcciones de la parte no asignada de la matriz podría causar una violación de alias estricto?
Este es un comportamiento indefinido .
Se supone que foo apunta a un objeto (o al primero de una serie de objetos) de tipo int[10] . Esto se considera un objeto de tipo matriz , definido en la sección 6.2.5p20 del estándar C
Un tipo de matriz describe un conjunto de objetos no vacíos asignados de forma contigua con un tipo de objeto miembro particular, denominado tipo de elemento. El tipo de elemento debe estar completo siempre que se especifique el tipo de matriz. Los tipos de matriz se caracterizan por su tipo de elemento y por el número de elementos de la matriz. Se dice que un tipo de matriz se deriva de su tipo de elemento, y si su tipo de elemento es T, el tipo de matriz a veces se denomina ''matriz de T''. La construcción de un tipo de matriz a partir de un tipo de elemento se denomina "derivación de tipo de matriz".
La parte que he resaltado en negrita es la parte importante. Por lo tanto, un int[10] es un conjunto asignado de forma contigua de 10 objetos de tipo int .
No asigna suficiente espacio, por lo que la expresión *foo que tiene el tipo int[10] accede a un objeto de ese tipo, pero al hacerlo lee más allá del final de un segmento de memoria asignado.
Como @dbush describe en su respuesta , una matriz se define como un conjunto de objetos no vacíos asignados de forma contigua del tipo de elemento (C17 6.2.5/20). Claramente, entonces, malloc( sizeof( int ) * 5 ) no asigna suficiente espacio para un int[10] .
Pero me resultó difícil respaldar formalmente la última parte de esa respuesta, alegando que el diferencial de tamaño hace que (por ejemplo) (*foo)[4] tenga un comportamiento indefinido. Esa conclusión parece plausible, pero ¿dónde lo dice realmente el estándar?
Uno de los principales problemas aquí es que los objetos asignados (dinámicamente) no tienen un tipo declarado, solo, en algunas circunstancias, un tipo efectivo determinado por cómo son y cómo se ha accedido a ellos. (C17 6.5/6 y nota al pie 88). Sabemos que en caso de éxito, malloc(n) devuelve un puntero a un objeto de tamaño n (C17 7.22.3.4/2), pero ¿cómo atribuimos un comportamiento indefinido específicamente a la asociación con ese objeto de un tipo efectivo que describe objetos de tamaño mayor que n ?
Finalmente decidí que la mejor manera de conectar los puntos es la siguiente. Suponga que o es un objeto asignado de tamaño n , T es un tipo completo que tiene tamaño de sizeof(T) > n , y o se lee o escribe a través de un valor l de tipo T Entonces el párrafo 6.5/6 atribuye el tipo efectivo T al objeto o , pero debido a que el tamaño de o es insuficiente debemos concluir que su representación constituye una representación trampa del tipo T (C17 3.19.4). El párrafo 6.2.6.1/5 luego reitera la definición de "representación trampa" y nos lleva a donde queremos ir:
Ciertas representaciones de objetos no necesitan representar un valor del tipo de objeto. Si el valor almacenado de un objeto tiene una representación de este tipo y lo lee una expresión lvalue que no tiene tipo de carácter, el comportamiento no está definido. Si tal representación es producida por un efecto secundario que modifica todo o parte del objeto por una expresión lvalue que no tiene tipo de carácter, el comportamiento es indefinido. Tal representación se llama representación trampa.
(Énfasis añadido.)