Linux内核中container_of()宏中的(char *)强制转换

7
#define container_of(ptr, type, member) ({                      \
    const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
    (type *)( (char *)__mptr - offsetof(type,member) );})

我已经知道第一行用于类型检查,但对于第二行,为什么要进行(char *)转换?我没有找到解释。
我自己做了一些实验代码,即使没有转换,结果似乎也很好。

1个回答

7

这是为了计算字节级地址而进行的操作。否则,指针算术运算将以指向的类型为基础,这会导致offsetof计算偏移量时出现错误。

在像这样的低级原语内部,将类型转换为char *非常普遍,因为有时您需要将内存视为“字节数组”并在该级别上进行操作。

考虑以下结构作为示例:

struct demo {
  int foo;
  float bar;
};

现在,如果我们这样做:

struct demo test;
float *intptr = &test.bar;

我们应该能够使用container_of()获取指向test的指针:
struct demo *owner = container_of(intptr, struct demo, bar);

这将扩展为:
struct demo *owner = {(
    const typeof( ((struct demo*)0)->bar) *__mptr = (intptr);
    (struct demo*)( (char *)__mptr - offsetof(struct demo,bar) );})

所以第一行声明了一个 float 指针 __mptr 并将参数值复制给它。
第二行将成员指针强制转换为 char *,然后减去 struct demobar 的偏移量(如果 sizeof (int) 是 4,则为 4),从而回到整个结构体 test 的地址。
如果没有进行 char * 的强制转换,4 将被解释为 float(因为 __mptrfloat *),这显然会向上溢出得太多(16 字节而不是 4 字节,假设 float 也是 4 字节),导致严重的错误。
请注意,包含 container_of() 扩展的 {( 语法是 GCC 的扩展;在标准 C 中无法使用。

请查看此页面以获取更多关于container_of()的内容。


如果那真的很重要,我怎么才能在没有它的情况下得到正确的结果呢?你能提供一个反例吗? - moeCake
字节级地址会不同吗?无论如何,地址都指向字节,强制转换不会改变其值。这是为了代码可读性吗?这与对齐有关吗? - Sevauk
@Sevauk 不,地址的值通常不会有所不同,但是当您对其进行算术运算(减法)时,情况会非常不同。 - unwind
谢谢您提供的详细信息和链接,但是即使使用您的情况,我仍然不明白为什么没有进行强制转换也能得到正确的结果。 - moeCake
哦...等等,好像是我的错,现在需要进行更多的测试。 - moeCake

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接