【问题标题】:Why is the offsetof macro necessary?为什么 offsetof 宏是必要的?
【发布时间】:2016-02-07 21:14:30
【问题描述】:

我是 C 语言的新手,刚刚学习了结构和指针。

我的问题与我最近看到的offsetof 宏有关。我知道它是如何工作的以及其背后的逻辑。

<stddef.h>文件中的定义如下:

#define offsetof(type,member) ((unsigned long) &(((type*)0)->member))

我的问题是,如果我有如下所示的结构:

struct test {
    int field1:
    int field2:
};

struct test var;

为什么我不能直接得到field2的地址为:

char * p = (char *)&var;
char *addressofField2 = p + sizeof(int);

而不是写这样的东西

field2Offset = offsetof (struct test, field2);

然后将偏移值添加到 var 的起始地址?

有什么不同吗?使用offsetof 效率更高吗?

【问题讨论】:

  • 如果您是 C 新手,请放弃 offsetof 等。普通编程不需要它,但只有初学者很可能会搞砸的高级功能。 - 没有冒犯,但一个非常善意的警告!

标签: c struct offsetof


【解决方案1】:

C 编译器通常会在 struct 的成员之间添加额外的填充位或字节,以提高效率并保持整数字对齐(在某些体系结构中需要避免 bus errors 并且在某些体系结构中是必需的以避免效率问题)。例如,在许多编译器中,如果你有这个struct

struct ImLikelyPadded {
    int x;
    char y;
    int z;
};

您可能会发现 sizeof(struct ImLikelyPadded) 是 12,而不是 9,因为编译器会在单字节 char y 的末尾和字大小的 int z 之间插入三个额外的填充字节。这就是 offsetof 如此有用的原因 - 它可以让您确定事情的真正位置,甚至可以考虑填充字节,并且具有高度可移植性。

【讨论】:

  • 另外,实现在宏中不使用 UB 的唯一原因是它实现。
【解决方案2】:

与数组不同,struct 的内存布局并不总是连续的。编译器可能会添加额外的字节,以对齐内存。这称为padding

由于填充,我们很难手动找到成员的位置。这也是为什么我们总是使用 sizeof 来查找结构体大小的原因。

Offsetof ,宏可以让你找出结构的成员与结构的分层位置的距离,偏移量。

如果在 Linux 内核的 container_of 宏中看到 offsetof,则可以智能使用。这个宏让你在给定成员地址的情况下找出节点在通用包含双向链表中的起始位置

【讨论】:

    【解决方案3】:

    正如其他答案中已经提到的,填充是原因之一。我不会重复已经说过的话。

    使用offsetof 宏而不是手动计算偏移量的另一个很好的理由是您只需编写一次。想象一下,如果您需要更改field1 的类型或在field2 前面插入或删除一个或多个字段,会发生什么情况。使用您的手工计算,您必须找到并更改它的所有出现。缺少其中一个会产生难以发现的神秘错误。

    使用offsetof 编写的代码在这种情况下不需要任何更新。编译器会在下一次编译时处理所有事情。

    更重要的是,使用offsetof 的代码更加清晰。宏是标准的,它的功能是documented。阅读代码的程序员同行会立即理解它。要理解手工编写的代码尝试的内容并不容易。

    【讨论】:

      猜你喜欢
      • 2011-02-03
      • 1970-01-01
      • 2011-10-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多