example - typedef c language
¿Está bien acceder más allá del tamaño de una estructura a través de la dirección del miembro, con suficiente espacio asignado? (3)
Específicamente, ¿está bien el siguiente código, la línea debajo del marcador?
struct S{
int a;
};
#include <stdlib.h>
int main(){
struct S *p;
p = malloc(sizeof(struct S) + 1000);
// This line:
*(&(p->a) + 1) = 0;
}
La gente ha discutido here , pero nadie ha dado una explicación o referencia convincente.
Sus argumentos están en una base ligeramente diferente, pero esencialmente el mismo
typedef struct _pack{
int64_t c;
} pack;
int main(){
pack *p;
char str[9] = "aaaaaaaa"; // Input
size_t len = offsetof(pack, c) + (strlen(str) + 1);
p = malloc(len);
// This line, with similar intention:
strcpy((char*)&(p->c), str);
// ^^^^^^^
C estándar garantiza que
§6.7.2.1 / 15:
[...] Un puntero a un objeto de estructura, convenientemente convertido, apunta a su miembro inicial (o si ese miembro es un campo de bits, luego a la unidad en la que reside), y viceversa. Puede haber un relleno sin nombre dentro de un objeto de estructura, pero no al principio.
&(p->a)
es equivalente a (int *)p
. &(p->a) + 1
será la dirección del elemento de la segunda estructura. En este caso, solo hay un elemento allí, no habrá ningún relleno en la estructura, así que esto funcionará, pero donde habrá relleno este código se romperá y dará lugar a un comportamiento indefinido.
Este es un comportamiento indefinido, ya que está accediendo a algo que no es una matriz ( int a
dentro de la struct S
) como una matriz, y fuera de límites en eso.
La forma correcta de lograr lo que desea es usar una matriz sin un tamaño como el último miembro de la struct
:
#include <stdlib.h>
typedef struct S {
int foo; //avoid flexible array being the only member
int a[];
} S;
int main(){
S *p = malloc(sizeof(*p) + 2*sizeof(int));
p->a[0] = 0;
p->a[1] = 42; //Perfectly legal.
}
La intención, al menos desde la estandarización de C en 1989, ha sido que las implementaciones puedan verificar los límites de la matriz para los accesos a la matriz.
El miembro p->a
es un objeto de tipo int
. C11 6.5.6p7 dice que
7 Para los fines de [operadores aditivos] un puntero a un objeto que no es un elemento de una matriz se comporta igual que un puntero al primer elemento de una matriz de longitud uno con el tipo de objeto como su tipo de elemento .
Así
&(p->a)
es un puntero a un int
; pero también es como si fuera un puntero al primer elemento de una matriz de longitud 1, con int
como el tipo de objeto.
Ahora 6.5.6p8 permite calcular &(p->a) + 1
que es un puntero justo después del final de la matriz, por lo que no hay un comportamiento indefinido. Sin embargo, la desreferencia de dicho puntero no es válida. Del apéndice J.2 donde está explicado, el comportamiento no está definido cuando:
La adición o sustracción de un puntero hacia, o más allá de, un objeto de matriz y un tipo entero produce un resultado que apunta justo más allá del objeto de matriz y se utiliza como el operando de un operador unario
*
que se evalúa (6.5.6).
En la expresión anterior, solo hay una matriz, la (como si) con exactamente 1 elemento. Si se anula la referencia a &(p->a) + 1
, se accede a la matriz con longitud 1 fuera de límites y se produce un comportamiento indefinido , es decir,
Comportamiento [...], para el cual [El C11] Norma no impone requisitos
Con la nota diciendo que :
El comportamiento indefinido varía desde ignorar completamente la situación con resultados impredecibles, hasta comportarse durante la traducción o la ejecución del programa de una manera documentada característica del entorno (con o sin la emisión de un mensaje de diagnóstico), hasta terminar una traducción o ejecución (con la emisión de un mensaje de diagnóstico).
Que el comportamiento más común es ignorar completamente la situación , es decir, comportarse como si el puntero hiciera referencia a la ubicación de la memoria justo después, no significa que otro tipo de comportamiento no sea aceptable desde el punto de vista del estándar; el estándar permite todo lo imaginable y un resultado inimaginable.
Se ha afirmado que el texto estándar del C11 se ha escrito vagamente, y la intención del comité debería ser que esto realmente se permita, y anteriormente hubiera estado bien. No es cierto. Lea la parte de la respuesta del comité al [Informe de Defecto # 017 con fecha del 10 de diciembre de 1992 a C89 ].
Pregunta 16
[...]
Respuesta
Para una matriz de matrices, la aritmética de puntero permitida en la subcláusula 6.3.6, página 47, líneas 12-40 debe entenderse interpretando el uso de la palabra objeto como que denota el objeto específico determinado directamente por el tipo y valor del puntero, no otros objetos relacionados con ese por contigüidad . Por lo tanto, si una expresión excede estos permisos, el comportamiento no está definido. Por ejemplo, el siguiente código tiene un comportamiento indefinido:
int a[4][5]; a[1][7] = 0; /* undefined */
Algunas implementaciones conformes pueden optar por diagnosticar una violación de límites de matriz , mientras que otras pueden optar por interpretar dichos intentos de acceso con éxito con la semántica extendida obvia .
(negrita énfasis mío)
No hay ninguna razón por la que no se transfiera lo mismo a los miembros escalares de las estructuras, especialmente cuando 6.5.6p7 dice que se debe considerar que un puntero a ellos se comporta igual que un puntero al primer elemento de una matriz de longitud uno con el tipo del objeto como su tipo de elemento .
Si desea abordar las struct
consecutivas, siempre puede tomar el puntero al primer miembro y convertirlo en puntero a la struct
y avanzar en su lugar:
*(int *)((S *)&(p->a) + 1) = 0;