0

I'm reading the inotify man page here and i struggle to understand the following comment in the example

Some systems cannot read integer variables if they are not properly aligned. On other systems, incorrect alignment may decrease performance. Hence, the buffer used for reading from the inotify file descriptor should have the same alignment as struct inotify_event.

And this is the buffer declaration+definition

char buf[4096]
        __attribute__ ((aligned(__alignof__(struct inotify_event))));

I read the following pdf document that gives a basic understanding of memory alignment access issues.

I would like to understand both the inotify comment and also having some hints and links for further understand the alignment problem. For example i always hear that there's no alignment problem for variables allocated on the stack. The problem arise only for buffers whose data gets reinterpreted.

Ry-
  • 199,309
  • 51
  • 404
  • 420
  • The worse part is that this is *still* strict aliasing violation – Antti Haapala Oct 21 '18 at 21:50
  • this might be of interest: https://stackoverflow.com/questions/46790550/c-undefined-behavior-strict-aliasing-rule-or-incorrect-alignment – Antti Haapala Oct 21 '18 at 21:50
  • `there's no alignment problem for variables allocated on the stack`. That's strange. `char buf[20]; long *a = &buf[1]; printf("%ld\n", *a);` will cause unaligned access to long object, and happens on stack. – KamilCuk Oct 21 '18 at 21:51
  • It means exactly what it says: On some systems, if you try to access a 4-byte integer, which is not aligned to 4 byte in memory, your program crashes (i.e. with a SIGBUS). On other systems, the integer can be accessed with multiple memory accesses, which is slower. – Ctx Oct 21 '18 at 21:51
  • @antti Care to elaborate? I fail to see where does the strict aliasing violation happens, I'm inspecting the example from the man page. The underlying buf memory is filled with an array of `struct inotify_event` elements. The user code casts it and reads an array of `struct inotify_event` elements (`event = (const struct inotify_event *) ptr;`). Is the (struct inotify_event).name struct member making this cast strict alias violation? The pointer ` ptr += sizeof(struct inotify_event) + event->len` is nicely incremented. – KamilCuk Oct 21 '18 at 22:00
  • @KamilCuk https://stackoverflow.com/a/29676395/918959 – Antti Haapala Oct 21 '18 at 22:05
  • Should have been the same to having used a an array o struct inotify_event like inotify_event[100]? In this case the array should have been already aligned. Correct? – Filippo Cucchetto Oct 22 '18 at 07:28
  • @AnttiHaapala I fail to see how that example violates strict aliasing. The `read` returns an array of `struct inotfiy_event`s with an array of chars after it. There is no access using pointers different from the type of objects stored in the underlying memory, no strict alias violation happens. Can you pinpoint what pointer / expression violates strict alias violation? I have created [this thread](https://stackoverflow.com/questions/52920834/strict-alias-violation-or-alignment-violation-with-a-struct-with-flexible-array) however got no respose so I'll probably move it to codereview. – KamilCuk Oct 22 '18 at 08:55
  • @FilippoCucchetto In case of `inotify_event[100]` there is no place for the names, cause `inotify_event[0].name == &inotify_event[1]`. If you were to `inotify_event[0].name = 'a'` you would overwrite the first byte `intify_event[1].wd`. But in case of `inotify_event[100]` you can indeed assert that `(uintptr_t)(char*)(void*)inotify_event[i] % alignof(*inotify_event)` is true, ie. each array member is aligned with needed alignment for `struct inofify_event`. The hard part comes to allocating memory for the `name` member. – KamilCuk Oct 22 '18 at 08:58

2 Answers2

2

The struct inotify_event is declared like this:

     struct inotify_event {
           int      wd;       /* Watch descriptor */
           uint32_t mask;     /* Mask describing event */
           uint32_t cookie;   /* Unique cookie associating related
                                 events (for rename(2)) */
           uint32_t len;      /* Size of name field */
           char     name[];   /* Optional null-terminated name */
       };

The problem is with the flexible array member name. Name has no number inside the braces, that means that &((struct inotify_event*)0)->name[0] == offsetof(struct inotify_event, name), ie. the memory for elements inside the name member starts RIGHT AFTER the structure.

Now if we were to story one inotify event, we need additional space for the name after the structure. Dynamic allocation may look like this:

    char name[] = "this is the name of this event";
    struct inotify_event *obj = malloc(sizeof(*obj) * (strlen(name) + 1));
    obj->wd = smth;
    obj->mask = smth2;
    obj->len = strlen(name) + 1;
    // fun fact: obj->name = &obj[1] . So if you ware to place array of inotify_events, you would overwrite the second member here.
    memcpy(obj->name, name, obj->len);

The memory for the name structure member comes right after struct inotify_event and is allocated with the same malloc. So If we want to have an array of inotify_events and would copy them including the name, the next struct inotify_event may be unaligned.

Let's assume alignof(struct inotify_event) = 8 and sizeof(struct inotify_event) = 16 and char name[] = "A"; so strlen(name) = 1 (strlen excludes counting the terminating zero byte) and that we want to store an array of inotfiy_events inside a buffer. First we copy the struct - 16 bytes. Then we copy the name - 2 bytes (including zero byte). If we were to copy the next struct, it would be unaligned, cause we would copy it starting from the 18th byte (sizeof(struct inotfy_event) + strlen(name) + 1) in the buffer, which is not dividable by alignof(struct inotify_event). We need to insert 5 bytes of extra padding and we need to do that manually, after the first array member, so the next struct inotify_event will be copied into the 24th byte.

However, we need also to notify the user / application code of how much it needs to increment the pointer to get to the next struct array member. So we increment obj->len with the number of padding bytes. So obj->len is equal to strlen(name) + 1 + number of padding bytes inserted to make the next array member aligned or is equal to 0, in case of no name.

Inspect the example code in the manual page. In the loop where we loop through struct inotify_events there is the line:

ptr += sizeof(struct inotify_event) + event->len

The ptr is a char* pointer to the current / next array member. We need to increment the pointer not only by sizeof(struct inotify_event) but also by the number of strlen(name) + 1 bytes + inserted padding to the next array member. That way we can keep the array member aligned to their needed alignment. In the next position is the next struct inotify_event.

For more information browse about pointer arithmetics in C and flexible array struct member.

KamilCuk
  • 69,546
  • 5
  • 27
  • 60
  • 1
    so if i understood correctly the len field of inotify_event could be different from strlen of the name because the len field could add some more padding for letting the next struct in the array aligned. Correct? – Filippo Cucchetto Oct 22 '18 at 11:54
  • Correct. (... or len can be zero. That's why the check `if (event->len) printf("%s", event->name);` in that code) – KamilCuk Oct 22 '18 at 12:00
1

This is the old GCC non-standard way of specifying that the array buf needs to start at an address that would be a suitable starting address for struct inotify_event.

This can be in C11, C17 written as

char _Alignas(struct inotify_event) buf[4096];
Antti Haapala
  • 117,318
  • 21
  • 243
  • 279