offsetof 是什么?
offsetof 是一个在 C 标准库中定义的宏,它的全称是 "member offset"(成员偏移量),它的作用是获取一个结构体(struct)或联合体(union)中某个成员变量相对于结构体起始地址的偏移量(以字节为单位)。

这个偏移量是一个 size_t 类型的无符号整数值,它在内存布局、序列化、网络编程、底层系统编程等领域非常有用。
offsetof 的标准用法
offsetof 的使用非常简单,语法如下:
#include <stddef.h> // 必须包含这个头文件 offset = offsetof(struct_type, member_name);
参数说明:
struct_type: 结构体或联合体的类型名。member_name: 结构体或联合体中成员变量的名字。
返回值:

- 返回
member_name成员在struct_type中的起始地址与struct_type整体的起始地址之间的字节差。
示例代码
#include <stdio.h>
#include <stddef.h>
// 定义一个结构体
struct Person {
char name[20]; // 20 bytes
int age; // 4 bytes
double salary; // 8 bytes
};
int main() {
// 计算 name 成员的偏移量
size_t name_offset = offsetof(struct Person, name);
printf("Offset of 'name': %zu\n", name_offset); // 预期输出: 0
// 计算 age 成员的偏移量
size_t age_offset = offsetof(struct Person, age);
printf("Offset of 'age': %zu\n", age_offset); // 预期输出: 20
// 计算 salary 成员的偏移量
size_t salary_offset = offsetof(struct Person, salary);
printf("Offset of 'salary': %zu\n", salary_offset); // 预期输出: 24
// 为了验证,我们也可以手动计算一下
// sizeof(name) = 20
// sizeof(age) = 4
// 20 + 4 = 24, 这与 salary_offset 的结果一致
return 0;
}
输出结果(在大多数系统上):
Offset of 'name': 0
Offset of 'age': 20
Offset of 'salary': 24
注意:由于内存对齐的存在,
sizeof(struct Person)可能不等于20 + 4 + 8 = 32,在许多系统上,sizeof(struct Person)可能是 40,因为在age(4字节) 和salary(8字节) 之间可能会有 4 字节的填充,但offsetof只关心成员相对于结构体起始位置的偏移,salary的偏移量仍然是 24。
offsetof 的实现原理(核心)
offsetof 是一个宏,而不是一个函数,这是它的实现能够如此“神奇”的关键,C 标准并没有规定它的具体实现方式,但通常有两种主流的实现方法,第二种是更通用、更强大的方法。
利用 typeof (GCC/Clang 扩展)
在 GCC 和 Clang 编译器中,可以使用 typeof 关键字(C11 标准已引入 _Generic,可以更优雅地实现类似功能)。

// 这种方法依赖于编译器扩展,不是标准C,但很直观
#define offsetof(TYPE, MEMBER) \
((size_t) &((TYPE *)0)->MEMBER)
工作原理分解:
(TYPE *)0: 将整数0强制转换为指向TYPE类型的指针,我们得到一个虚拟的、地址为0的结构体指针。((TYPE *)0)->MEMBER: 通过这个虚拟指针访问它的成员MEMBER,根据 C 语言的指针运算规则,pointer->member的地址实际上是pointer的地址加上member的偏移量。- 因为
pointer的地址是0,&((TYPE *)0)->MEMBER的计算结果就是0 + offset_of_MEMBER,也就是offset_of_MEMBER。
- 因为
(size_t) ...: 将计算出的地址(一个整数)转换为size_t类型,以确保返回值的类型正确。
优点:非常简洁,易于理解。
缺点:依赖于 typeof(或类似的编译器特性),不是 100% 的标准 C。
标准 C 宏实现(通用且强大)
这是在标准 C 中实现 offsetof 的最常见、最可靠的方法,它不依赖任何编译器扩展。
// 标准C中 offsetof 的典型实现
#define offsetof(TYPE, MEMBER) \
((size_t) &((TYPE *)0)->MEMBER)
Wait, this looks the same as the GCC version! And it is. The magic is that even without typeof, this expression is well-defined by the C standard.
为什么它在标准 C 中是合法的?
关键在于 C 标准对指针算术的特殊规定,即使我们解引用了一个空指针 (TYPE *)0,我们只取其地址 &...,而不实际访问该地址上的值。
C 标准允许对任何指针(包括空指针)进行取地址操作 &,只要我们不试图解引用(dereference)该指针来读取或写入数据。& 操作符本身不触发内存访问。
&((TYPE *)0)->MEMBER 这段代码的执行过程是:
- 编译器看到
(TYPE *)0,知道这是一个指向TYPE的、地址为 0 的指针。 - 编译器知道
MEMBER在TYPE中的偏移量是X字节。 - 编译器计算
&((TYPE *)0)->MEMBER的结果,它直接得出一个地址值X,这个过程完全在编译时完成,是纯粹的地址计算,不涉及任何运行时的内存访问。 (size_t)将这个地址值X转换为无符号整数类型。
这个宏在所有符合标准的 C 编译器中都能正确工作,并且是安全的。
offsetof 的实际应用
offsetof 在很多底层编程场景中都非常有用。
应用 1:手动访问结构体成员
当你只有一个指向结构体的 void* 指针,但想通过成员名字来访问成员时,offsetof 就派上用场了。
#include <stdio.h>
#include <stddef.h>
#include <string.h>
struct Point {
int x;
int y;
};
void print_point(void* p) {
// 使用 offsetof 来计算成员的地址
int* x_ptr = (int*)((char*)p + offsetof(struct Point, x));
int* y_ptr = (int*)((char*)p + offsetof(struct Point, y));
printf("Point is: (%d, %d)\n", *x_ptr, *y_ptr);
}
int main() {
struct Point my_point = {10, 20};
print_point(&my_point); // 传递结构体的地址
return 0;
}
应用 2:处理数据序列化和反序列化
在网络传输或文件存储中,经常需要将结构体数据转换成字节流(序列化),或者从字节流还原成结构体(反序列化)。offsetof 可以帮助你精确定位每个成员在字节流中的位置。
#include <stdio.h>
#include <stddef.h>
#include <string.h>
struct DataPacket {
char header[4];
int id;
float value;
};
// 序列化函数
void serialize(const struct DataPacket* packet, char* buffer) {
// 使用 memcpy 和 offsetof 来安全地复制数据
memcpy(buffer + offsetof(struct DataPacket, header), packet->header, sizeof(packet->header));
memcpy(buffer + offsetof(struct DataPacket, id), &packet->id, sizeof(packet->id));
memcpy(buffer + offsetof(struct DataPacket, value), &packet->value, sizeof(packet->value));
}
// 反序列化函数
void deserialize(const char* buffer, struct DataPacket* packet) {
memcpy(packet->header, buffer + offsetof(struct DataPacket, header), sizeof(packet->header));
memcpy(&packet->id, buffer + offsetof(struct DataPacket, id), sizeof(packet->id));
memcpy(&packet->value, buffer + offsetof(struct DataPacket, value), sizeof(packet->value));
}
int main() {
struct DataPacket original = {'T', 'X', 'T', 1, 3.14f};
char serialized_buffer[sizeof(struct DataPacket)];
serialize(&original, serialized_buffer);
printf("Serialized.\n");
struct DataPacket received;
deserialize(serialized_buffer, &received);
printf("Deserialized: id=%d, value=%f\n", received.id, received.value);
return 0;
}
offsetof是一个宏,用于计算结构体/联合体成员的偏移量。- 核心实现:
((size_t) &((TYPE *)0)->MEMBER),它通过在地址 0 处进行虚拟的指针运算来得到偏移量。 - 安全性:该宏是安全的,因为它只进行地址计算,不实际访问内存,符合 C 标准。
- 用途:在需要动态访问结构体成员、处理内存布局、数据序列化等底层编程任务中不可或缺。
- 使用前提:必须包含
<stddef.h>头文件。
