C offsetof宏如何工作?

Sam*_*iew 11 c macros offset

可能重复:
为什么这个C代码有效?
你如何在struct上使用offsetof()?

我在互联网上读到了这个偏移宏,但它没有解释它的用途.

#define offsetof(a,b) ((int)(&(((a*)(0))->b)))
Run Code Online (Sandbox Code Playgroud)

它想做什么以及使用它有什么好处?

Eam*_*ain 32

R ..在回答问题的第二部分时是正确的:使用现代C编译器时不建议使用此代码.

但要回答你问题的第一部分,这实际上是做什么的:

(
  (int)(         // 4.
    &( (         // 3.
      (a*)(0)    // 1.
     )->b )      // 2.
  )
)
Run Code Online (Sandbox Code Playgroud)

从内到外,这是......

  1. 将值零转换为struct指针类型 a*
  2. 获取此(非法放置)struct对象的struct字段b
  3. 获取此b字段的地址
  4. 将地址转换为 int

从概念上讲,这是将一个struct对象放在内存地址为零,然后找出特定字段的地址是什么.这可以让您找出结构中每个字段的内存偏移量,这样您就可以编写自己的序列化器和反序列化器来将结构转换为字节数组.

当然,如果您实际取消引用零指针,程序将崩溃,但实际上一切都在编译器中发生,并且在运行时没有实际的零指针被取消引用.

在大多数原始系统中,C运行的大小int为32位且与指针相同,所以这实际上有效.

  • 优秀!谢谢。对我来说,关键是“将一个结构对象放在内存地址零处,然后找出特定字段的地址是什么”。 (2认同)

R..*_*R.. 13

它没有优点,不应该使用,因为它调用未定义的行为(并使用错误的类型 - int而不是size_t).

对于需要结构中元素偏移的情况,C标准定义了一个实际适用的offsetofstddef.h,例如:

#include <stddef.h>

struct foo {
    int a;
    int b;
    char *c;
};

struct struct_desc {
    const char *name;
    int type;
    size_t off;
};

static const struct struct_desc foo_desc[] = {
    { "a", INT, offsetof(struct foo, a) },
    { "b", INT, offsetof(struct foo, b) },
    { "c", CHARPTR, offsetof(struct foo, c) },
};
Run Code Online (Sandbox Code Playgroud)

这将允许您以编程方式填充struct foo按名称的字段,例如,在读取JSON文件时.

  • 你引用的文字是无关紧要的.没有指向不完整或对象类型的指针转​​换为伪造宏中指向void的指针. (5认同)
  • 来自`stddef.h`的标准`offsetof`宏不会调用UB.定义自己的hack以这种方式计算偏移量会调用UB. (4认同)
  • @AdrianCornish:**实现**允许定义`offsetof`,但只要它实现了正确的行为.**您的应用程序**没有此权限,因为它无法定义任何行为; 它只能使用已经定义的语言结构.这就是C的工作方式. (4认同)
  • 6.5.2.3不使用"dereference"一词,而是将其指定为"第一个表达式指向的对象的指定成员".由于`(a*)(0)`不指向类型为'a`的对象,因此行为未定义(由于未定义). (4认同)
  • @Adrian:他没有说,"定义自己的宏版本会导致未定义的行为." 他特意说,"定义你自己的黑客以计算抵消**这样**确实会调用UB." 在代码中,此时:`((a*)(0)) - >`你通过解除引用null来调用未定义的行为. (2认同)

Ada*_*eld 5

它正在查找中特定成员的字节偏移量struct。例如,如果您具有以下结构:

struct MyStruct
{
    double d;
    int i;
    void *p;
};
Run Code Online (Sandbox Code Playgroud)

然后,您将拥有offsetOf(MyStruct, d) == 0offsetOf(MyStruct, i) == 8offsetOf(MyStruct, p) == 12(即,d从结构的开头开始,名为的成员为0个字节,等等)。

它的工作方式是假装您的结构实例存在于地址0(该((a*)(0))部分),然后获取所需结构成员的地址并将其转换为整数。尽管在地址0处取消引用对象通常是一个错误,但可以采用该地址,因为address-of运算符&和成员取消引用会->相互抵消。

它通常用于广义序列化框架。如果您有用于在某种有线数据(例如,文件中的字节或来自网络的字节)和内存中数据结构之间进行转换的代码,则通常很方便地创建从成员名称到成员偏移的映射,以便您可以序列化或以通用方式反序列化值。