ruach
ruach

Reputation: 1469

Why this union has char array at the end?

While I was reading a Linux kernel implementation for RCU lock primitives, I ended up reaching the below code.

#define WRITE_ONCE(x, val) \
({                                                      \
        union { typeof(x) __val; char __c[1]; } __u =   \
                { .__val = (__force typeof(x)) (val) }; \
        __write_once_size(&(x), __u.__c, sizeof(x));    \
        __u.__val;                                      \
})


static __always_inline void __write_once_size(volatile void *p, void *res, int size)
{
        switch (size) {
        case 1: *(volatile __u8 *)p = *(__u8 *)res; break;
        case 2: *(volatile __u16 *)p = *(__u16 *)res; break;
        case 4: *(volatile __u32 *)p = *(__u32 *)res; break;
        case 8: *(volatile __u64 *)p = *(__u64 *)res; break;
        default:
                barrier();
                __builtin_memcpy((void *)p, (const void *)res, size);
                barrier();
        }
}

As far as I can tell, union ending with the char array having the size of the previous part of the union allows you to access memory assigned to the union in byte granularity.

For example,

#define UNION_PARTIAL \
    int a;            \
    int b;            \
    char *c;          \

union union_partial {
    UNION_PARTIAL
};

union union_full {
    UNION_PARTIAL
    char bytes[sizeof(union union_partial)];
};

However, it seems that the union used in the WRITE_ONCE macro is not declared to provide fine granularity accesses to the union memory. The below code may be used instead, but I don't know why we need char __c[1].

#define WRITE_ONCE(x, val) \
({                                                      \
        union { typeof(x) __val; char __c[1]; } __u =   \
                { .__val = (__force typeof(x)) (val) }; \
        __write_once_size(&(x), &(__u.val), sizeof(x)); \
        __u.__val;                                      \
})

is it only because to reduce burden for a programmer to type & in front of the __u.val ?

Upvotes: 2

Views: 363

Answers (1)

KamilCuk
KamilCuk

Reputation: 141603

Let's dwell into READ_ONCE(). The C compiler will complain if passing a const pointer into a function that takes a void * pointer. The __read_once_size is declared as static __always_inline void __read_once_size(volatile void *p, void *res, int size). The macro was declared as:

#define READ_ONCE(x) \
    ({ typeof(x) __val; __read_once_size(&x, &__val, sizeof(__val)); __val; })

will cause warnings when the typedef has const in it, I think like:

typedef const int type_t;
struct a_s {
   type_t m;
};
struct a_s a = { .m = 1; };
type_t b = READ_ONCE(a.m);

In such usage typeof(x) is const int, so &__val is const int*, which will cause warnings/errors when casting into void*. In such usage the const is not cast away on typeof, so we pass a const * pointer to the __write_once_size function. So the author decided to use a "union trick" to cast the constness away by passing a pointer to the array starting at the same place as the value. (One could also do some strange casts (const void*)(uintptr_t)(void*), but it wouldn't be so portable).

The author in this commit explained this and changed the READ_ONCE macro:

- ({ typeof(x) __val; __read_once_size(&x, &__val, sizeof(__val)); __val; })
+ ({ union { typeof(x) __val; char __c[1]; } __u; __read_once_size(&(x), __u.__c, sizeof(x)); __u.__val; })

Probably to be consistent and accommodate with READ_ONCE() changes, the WRITE_ONCE() macro was changed accordingly in this commit.

Upvotes: 1

Related Questions