当我在阅读RCU锁原语的Linux内核实现时,最终遇到了以下代码。
#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();
}
}
据我所知,以以联合的前一部分大小的char数组结尾的联合允许您访问按字节粒度分配给联合的内存。
例如
#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)];
};
但是,似乎未声明在WRITE_ONCE宏中使用的联合会提供对联合内存的精细访问。可以改用下面的代码,但是我不知道为什么我们需要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; \
})
仅仅是因为减轻了程序员在__u.val前面键入&的负担?
答案 0 :(得分:1)
让我们谈谈READ_ONCE()
。如果将const
指针传递给采用void *
指针的函数,C编译器将抱怨。 __read_once_size
被声明为static __always_inline void __read_once_size(volatile void *p, void *res, int size)
。该宏声明为:
#define READ_ONCE(x) \
({ typeof(x) __val; __read_once_size(&x, &__val, sizeof(__val)); __val; })
我认为,当typedef中包含const时,会引起警告。
typedef const int type_t;
struct a_s {
type_t m;
};
struct a_s a = { .m = 1; };
type_t b = READ_ONCE(a.m);
在这种用法中,typeof(x)
是const int
,所以&__val
是const int*
,当转换为void*
时会引起警告/错误。在这种用法中,const
不会被抛弃在typeof
上,因此我们将const *
指针传递给__write_once_size
函数。因此,作者决定使用“联合技巧”,通过将指针传递到与值相同的位置处的数组来摆脱常量性。 (一个人也可以进行一些奇怪的强制转换(const void*)(uintptr_t)(void*)
,但它并不那么可移植)。
this commit中的作者对此进行了解释,并更改了READ_ONCE
宏:
- ({ 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; })
为了保持一致并适应READ_ONCE()
的更改,WRITE_ONCE()
宏在this commit中进行了相应的更改。