Imagine a structure like this:
struct test_s { int i; size_t namelen; char name[]; };
Now I would like to return an array of such structures to user code, also using a generic function which takes a void *
pointer:
size_t test_read(void *buf, size_t bufsize);
Each of the array elements would need to aligned to struct s_s
alignment. In each of array element the namelen
is adjusted, so it is greater or equal then strlen(name)
, but with additional "unused" bytes in between array members, so that next array member starts at memory bank dividable by alignof(struct s_s)
.
So we can know/assert that having any struct test_s *t
it is true that t->namelen % alignof(struct test_s) == 0
and t->namelen == 0 || t->namelen >= strlen(t->name)
.
Now image that test_read
returns 4 of struct test_s
structures. We can iterate through them, but adding each loop the sizeof(struct test_s)
and adding the t->namelen
of the current already processed member pnt += sizeof(struct test_s) + t->namelen
. The "unused" "padding" bytes between array members and strings are left uninitialized and the intention is that they are never to be accessed.
#include <stdio.h> #include <stddef.h> #include <assert.h> #include <string.h> #include <stdlib.h> #include <stdint.h> #include <stdalign.h> // 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 < &buf[readlen]; pnt += sizeof(*test) + test->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 : "" ); } }
- Does this code has any strict aliasing violations?
- Does this code can unaligned access any objects?
- Does this code has any undefined behaviors?
- This code is based on the example in man 7 inotify and I intended for it to be very similar. The example there iterates over
struct inotify_event
s using the same principle. Does this example has any undefined behaviours?
I say there is no strict aliasing violation. I never access an underlying object using different pointers, the pointers are always struct test_s*
and char*
, although arithmetic is done using char*
pointers.
Live version of the code available at onlinegdb.
The question originates from the comments from this question.