
offsetof是 C/C++ 中的一个宏,用于获取结构体/类中成员的字节偏移量。
#define offsetof(type, member) /* 编译器实现 */
[1] https://en.cppreference.com/w/cpp/types/offsetof.html
•
返回指定成员在结构体/类中的字节偏移量
•
满足每个成员的对齐要
If type is not a PODT ype(until C++11)standard-layout type(since C++11), the result of offsetof is undefined
•
在 C++ 中,POD(Plain Old Data) 类型指的是与 C 语言中的结构体和基础类型行为类似的类型。
•
POD 类型具有非常简单的内存模型,可以在没有额外复杂语义的情况下被安全地拷贝、传递、初始化等。它的内存布局在不同的编译器和平台上是可预见的,符合 C 语言的内存模型
•
类型要求:必须是标准布局类型(standard-layout type)
//判断是否 pod 或者标准 布局
https://en.cppreference.com/w/cpp/types/is_standard_layout.html
https://en.cppreference.com/w/cpp/types/is_pod.html
#include <type_traits>
struct A { int m; };
static_assert(std::is_standard_layout_v<A> true);
class B: public A { int m; };
static_assert(std::is_standard_layout_v<B> false);
struct C { virtual void foo(); };
static_assert(std::is_standard_layout_v<C> == false);
int main()
{}
POD 类型的规则细节
•
不能有自定义的构造函数、析构函数或拷贝构造函数。
•
不能有虚函数或虚基类。
•
成员变量的访问控制符(public、private、protected)不能混合使用,即成员变量的访问权限必须一致。
•
不能有带有初始值的非静态数据成员。
POD 类型的概念非常重要,特别是在需要与 C 语言兼容的场合。它确保类型在内存中的布局是可预见的,可以用于:
•
与 C 语言互操作:POD 类型与 C 语言的结构体兼容,可以用于跨语言调用。
•
二进制序列化:POD 类型的内存布局非常简单,可以直接进行二进制的序列化和反序列化。
•
高效的拷贝和赋值:POD 类型可以进行位拷贝,不需要调用复杂的构造函数或析构函数,因此拷贝操作效率很高。
#include <cstddef>
#include <iostream>
struct S
{
char m0; // 偏移量 0
double m1; // 偏移量 8(可能有填充)
short m2; // 偏移量 16
char m3; // 偏移量 18
//private:
// private: int z; // warning: 'S' is a non-standard-layout type
};
int main() {
std::cout << "offset of char m0 = " << offsetof(S, m0) << '\n'
<< "offset of double m1 = " << offsetof(S, m1) << '\n'
<< "offset of short m2 = " << offsetof(S, m2) << '\n'
<< "offset of char m3 = " << offsetof(S, m3) << '\n';
}
输出示例:
offset of char m0 = 0offset of double m1 = 8offset of short m2 = 16offset of char m3 = 181
成员各自大小
•
m0 :类型 char → 大小 1 字节
•
m1 :类型 double → 大小 8 字节
•
m2 :类型 short → 大小 2 字节
•
m3 :类型 char → 大小 1 字节
成员大小加起来 = 1 + 8 + 2 + 1 = 12 字节
2
对齐要求 & 中间填充
•
double 通常要求 8 字节对齐。因为 m0 放在偏移 0(占 1 字节),下一个成员 m1 若直接放在偏移 1,会违反其 8 字节对齐要求。
•
因此编译器会插入中间填充字节,将 m1 安放在偏移 8。也就是在偏移 1–7 处插入 7 字节填充。
•
接下来,m1 占偏移 8–15(共 8 字节)。
•
然后 m2:要求 2 字节对齐,下一个可用偏移是 16 → 符合对齐;占 16–17 (2 字节)
•
然后 m3:要求 1 字节对齐,下一个可用偏移是 18 → 放在 18,大小 1 字节
3
尾部填充(尾对齐)
•
整个结构体的对齐要求通常是其 “最大成员对齐要求” 的值,即这里为 8 字节(因为有一个 double)。
•
最后一个成员结束在偏移 18 +1 = 19 字节位置。若结构体没有尾部填充,那么数组中第二个元素可能从偏移 19 开始,这将不能保证双精度类型在其自身元素内对齐。
•
因此,为了保证每个 S 在数组中起始位置也满足按 8 字节对齐,编译器会将结构体整体尺寸向上填充到 8 的倍数。下一个 ≥19 的 8 的倍数是 24。
•
所以在偏移 19-23 之间插入尾部填充 5 字节,使得 sizeof(S) = 24 字节。
struct A {
char c; // 1 字节
int i; // 4 字节
char d; // 1 字节
};
偏移地址 内容 说明
----------------------------------
0 c char 占 1 字节
1 padding 填充 3 字节,使下一个成员 i 按 4 字节对齐
2 padding
3 padding
----------------------------------
4~7 i int 占 4 字节
----------------------------------
8 d char 占 1 字节
9 padding 填充,为保证整体结构体大小是 4 的倍数
10 padding
11 padding
----------------------------------
sizeof(A) = 12 字节
分析:
•
成员大小之和 = 1 + 4 + 1 = 6 字节。
•
假设 int 要 4 字节对齐。于是布局可能是:
•
c 放在偏移 0(占 1 字节)
•
插入填充 3 字节,使下一个 i 从偏移 4 开始
•
i 占偏移 4-7(4 字节)
•
接着 d 放在偏移 8(1 字节)
•
然后尾部可能填充,使整个结构体大小为 12
•
最终你可能发现 sizeof(A) == 12
struct B {
double d; // 8 字节
char c; // 1 字节
short s; // 2 字节
};
分析:
代码:github.com:watchpoints/master-cpp.git
#include <stdio.h>
#include <stddef.h>
struct B
{
double d;
char c;
short s;
};
int main()
{
printf("Sizeof(struct B) = %zu\n", sizeof(struct B));
printf("Offset of d: %zu\n", offsetof(struct B, d));
printf("Offset of c: %zu\n", offsetof(struct B, c));
printf("Offset of s: %zu\n", offsetof(struct B, s));
return 0;
}
/*
Sizeof(struct B) = 16
Offset of d: 0
Offset of c: 8
Offset of s: 10
*/
container_of 是kernel代码中利用C语言特性做的一个精妙设计。
它的作用顾名思义,就是已知某个变量,通过这个变量找到包含它的结构体变量。
作用:通过结构体的某个成员变量地址找到该结构体的首地址。
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/include/linux/container_of.h?h=v5.16.3
参数 | 含义 | 示例说明 |
|---|---|---|
ptr | 指向结构体成员的指针 | 比如,一个指向链表节点 list_head *node的指针。 |
type | 该成员所属的整个结构体的类型 | 比如,包含该链表节点的自定义结构体 struct my_data。 |
member | 该成员在结构体内部的名字 | 比如,链表节点在 struct my_data中定义的字段名 list。 |

例子:
container_of 宏最经典的应用是在 Linux 内核的链表实现中
内核实现了一种侵入式链表,链表节点被嵌入到业务数据结构中
#include <linux/container_of.h>
//定义宿主结构体(其中可以包含任意个成员,且成员可以是任意排列顺序)
struct demo {
char *name;
struct list_head list; // 链表节点嵌入其中
};
//从list获取name
void demo_list_to_name(struct list_head *lst)
{
struct demo *dm = container_of(lst, strcut demo, list);
printk(KERN_INFO "%s\n", dm->name);
}
//通过给子函数传递demo结构体中的list的指针,找到demo中的name变量
void demo_main(void)
{
struct demo dm;
dm.name = "Demo";
demo_list_to_name(&dm->list);
}
1
https://en.cppreference.com/w/cpp/types/offsetof.html
2
https://stackoverflow.com/questions/119123/why-isnt-sizeof-for-a-struct-equal-to-the-sum-of-sizeof-of-each-member
3
https://zhuanlan.zhihu.com/p/463588640