Imagina una estructura como esta:
struct test_s { int i; size_t namelen; char name[]; };
Ahora me gustaría devolver una matriz de tales estructuras al código de usuario, también usando una función genérica que toma un puntero void *
:
size_t test_read(void *buf, size_t bufsize);
Cada uno de los elementos de la matriz debería alinearse con la alineación de la struct s_s
. En cada uno de los elementos de la matriz, el namelen
se ajusta, por lo que es mayor o igual que strlen(name)
, pero con bytes “no utilizados” adicionales entre los miembros de la matriz, de modo que el siguiente miembro de la matriz comienza en el banco de memoria divisible por alignof(struct s_s)
.
Así que podemos saber / afirmar que al tener cualquier struct test_s *t
es cierto que t->namelen % alignof(struct test_s) == 0
y t->namelen == 0 || t->namelen >= strlen(t->name)
t->namelen == 0 || t->namelen >= strlen(t->name)
.
Ahora la imagen que test_read
devuelve 4 de struct test_s
. Podemos iterar a través de ellos, pero agregando a cada bucle el sizeof(struct test_s)
y agregando el t->namelen
del miembro actual ya procesado pnt += sizeof(struct test_s) + t->namelen
. Los bytes de “relleno” “no utilizados” entre los miembros de la matriz y las cadenas se dejan sin inicializar y la intención es que nunca se pueda acceder a ellos.
#include #include #include #include #include #include #include // out structure struct test_s { int i; size_t namelen; char name[]; }; // stores an struct test_s object into buf and after it stores string as pointed to by name // returns sizeof(struct test_s) + strlen(name) size_t test_init(void *buf, size_t bufsize, int i, const char name[]) { assert(buf != NULL); assert(bufsize > sizeof(struct test_s)); size_t namelen; // if (t->namelen == 0) { that means that t->name is empty. } if (name) { namelen = strlen(name) + 1; assert(bufsize > sizeof(struct test_s) + namelen * sizeof(*((struct test_s*)0)->name)); } else { namelen = 0; } struct test_s * const t = buf; t->i = i; t->namelen = namelen; memcpy(t->name, name, namelen); return sizeof(*t) + namelen * sizeof(*t->name); } // works as test_init, but returned value is incremented to // make the `buf + returned value` aligned to struct test_s size_t test_init_aligned(void *buf, size_t bufsize, int i, const char name[]) { const size_t copied = test_init(buf, bufsize, i, name); // align the end of the struct to be aligned with struct test_s struct test_s * const t = buf; const size_t tmp = copied % alignof(struct test_s); const size_t to_aligned = tmp ? alignof(struct test_s) - tmp : 0; t->namelen += to_aligned; const size_t aligned_size = copied + to_aligned; return aligned_size; } // returns multiple struct test_s objects and stores them in buf // returns number of bytes written size_t test_read(void *buf, size_t bufsize) { char * const outbeg = buf; char *out = buf; char * const outend = &out[bufsize]; out += test_init_aligned(out, outend - out, 1, "first element"); out += test_init_aligned(out, outend - out, 2, "second element"); out += test_init_aligned(out, outend - out, 3, "third element"); out += test_init_aligned(out, outend - out, 4, NULL); return out - outbeg; } int main() { // our read buffer char alignas(struct test_s) buf[1024]; // read structs test_s with strings into our buffer const size_t readlen = test_read(buf, sizeof(buf)); const struct test_s *test = NULL; const char *pnt = NULL; for (pnt = &buf[0]; pnt namelen * sizeof(*test->name)) { test = (const struct test_s *)pnt; printf("i=%d len=%2zu name='%s'\n", test->i, test->namelen, test->namelen != 0 ? test->name : "" ); } }
struct inotify_event
s usando el mismo principio. ¿Este ejemplo tiene algún comportamiento indefinido? Yo digo que no hay una violación estricta aliasing. Nunca struct test_s*
un objeto subyacente utilizando diferentes punteros, los punteros siempre son struct test_s*
y char*
, aunque la aritmética se realiza utilizando los punteros char*
.
Versión en vivo del código disponible en onlinegdb .
La pregunta se origina en los comentarios de esta pregunta .