为什么offsetof宏是必要的?

时间:2016-02-07 21:14:30

标签: c struct offsetof

我是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

3 个答案:

答案 0 :(得分:4)

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

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

你可能会发现sizeof(struct ImLikelyPadded)是12而不是9,因为编译器会在单字节char y的末尾和字大小的int z之间插入三个额外的填充字节。这就是为什么offsetof非常有用的原因 - 它可以让你确定事实上甚至是填充字节的因素,并且具有很高的可移植性。

答案 1 :(得分:0)

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

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

Offsetof,macro让你从结构的strating位置找出struct成员的距离,偏移量。

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

答案 2 :(得分:0)

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

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

使用offsetof编写的代码在这种情况下不需要任何更新。编译器负责下一次编译的所有内容。

更重要的是,使用offsetof的代码更加清晰。宏是标准的,其功能是documented。读取代码的同事程序员会立即理解它。理解手工制作的代码尝试起来并不容易。