为什么需要 offsetof 宏?
Why is the offsetof macro necessary?
我是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 编译器通常会在 struct
的成员之间添加额外的填充位或字节,以提高效率并保持整数字对齐(在某些体系结构中需要避免 bus errors并且在某些架构中需要避免效率问题)。例如,在许多编译器中,如果你有这个 struct
:
struct ImLikelyPadded {
int x;
char y;
int z;
};
您可能会发现 sizeof(struct ImLikelyPadded)
是 12,而不是 9,因为编译器会在单字节 char y
和字大小 [=15] 的末尾插入三个额外的填充字节=].这就是 offsetof
如此有用的原因 - 它可以让您确定事物的真正位置,甚至考虑填充字节并且具有高度可移植性。
与数组不同,结构的内存布局并不总是连续的。编译器可能会添加额外的字节,以对齐内存。这叫做padding
。
由于填充,我们很难手动找到成员的位置。这也是为什么我们总是使用 sizeof 来查找结构大小的原因。
Offsetof 宏让您从结构的开始位置找出结构成员的距离、偏移量。
如果在 Linux 内核的 container_of
宏中看到 offsetof,这是一种智能使用。这个宏可以让你找出节点的起始位置,给定成员的地址在一个通用的包含双向链表
正如其他答案中已经提到的,填充是原因之一。我不会重复已经说过的内容。
使用 offsetof
宏而不手动计算偏移量的另一个很好的理由是您只需编写一次。想象一下,如果您需要更改 field1
的类型或在 field2
前面插入或删除一个或多个字段,会发生什么情况。使用您手工制作的计算,您必须找到并更改它的所有出现。遗漏其中之一会产生难以发现的神秘错误。
使用offsetof
编写的代码在这种情况下不需要任何更新。编译器会在下一次编译时处理所有事情。
更重要的是,使用offsetof
的代码更加清晰。宏是标准的,它的功能是documented。阅读代码的程序员同事会立即理解它。手工编写的代码尝试的内容并不那么容易理解。
我是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 编译器通常会在 struct
的成员之间添加额外的填充位或字节,以提高效率并保持整数字对齐(在某些体系结构中需要避免 bus errors并且在某些架构中需要避免效率问题)。例如,在许多编译器中,如果你有这个 struct
:
struct ImLikelyPadded {
int x;
char y;
int z;
};
您可能会发现 sizeof(struct ImLikelyPadded)
是 12,而不是 9,因为编译器会在单字节 char y
和字大小 [=15] 的末尾插入三个额外的填充字节=].这就是 offsetof
如此有用的原因 - 它可以让您确定事物的真正位置,甚至考虑填充字节并且具有高度可移植性。
与数组不同,结构的内存布局并不总是连续的。编译器可能会添加额外的字节,以对齐内存。这叫做padding
。
由于填充,我们很难手动找到成员的位置。这也是为什么我们总是使用 sizeof 来查找结构大小的原因。
Offsetof 宏让您从结构的开始位置找出结构成员的距离、偏移量。
如果在 Linux 内核的 container_of
宏中看到 offsetof,这是一种智能使用。这个宏可以让你找出节点的起始位置,给定成员的地址在一个通用的包含双向链表
正如其他答案中已经提到的,填充是原因之一。我不会重复已经说过的内容。
使用 offsetof
宏而不手动计算偏移量的另一个很好的理由是您只需编写一次。想象一下,如果您需要更改 field1
的类型或在 field2
前面插入或删除一个或多个字段,会发生什么情况。使用您手工制作的计算,您必须找到并更改它的所有出现。遗漏其中之一会产生难以发现的神秘错误。
使用offsetof
编写的代码在这种情况下不需要任何更新。编译器会在下一次编译时处理所有事情。
更重要的是,使用offsetof
的代码更加清晰。宏是标准的,它的功能是documented。阅读代码的程序员同事会立即理解它。手工编写的代码尝试的内容并不那么容易理解。