自定义宏OFFSETOF 的实现

Separes

关注

阅读 44

2024-12-02

#include <stdio.h>

#define OFFSETOF(type, member) (size_t)&(((type*)0)->member)

struct Point {
    int x;
    float y;
};

int main() {
    printf("Offset of x: %zu\n", OFFSETOF(struct Point, x));
    printf("Offset of y: %zu\n", OFFSETOF(struct Point, y));
    return 0;
}

解释

1. (type*)0:

  • 这部分是一个类型转换操作,它将 0(也就是 NULL)转换为指向 type 类型的指针。在C和C++中,NULL 可以被隐式转换为任何指针类型,因此这个转换是合法的。这里我们显式地将其转换为 type* 类型的指针。
  • 这个表达式的结果是一个指向 type 类型的空指针,它不指向任何实际的内存地址,但是可以用来访问 type 类型的结构体或类的成员。

2. ((type*)0)->member:

  • 这部分使用了 -> 运算符来访问 member 成员。由于 (type*)0 是一个指针,所以这里实际上是在访问一个假想的、未分配内存的 type 实例的 member 成员。
  • 这个表达式的结果是一个对 member 成员的引用,这个成员属于一个理论上的、位于地址 0 的 type 实例。

3. &(((type*)0)->member):

  • 这部分是对上一步得到的成员引用取地址。由于 member 是一个假想的成员,取地址操作实际上是在获取 member 在 type 实例中的偏移量。
  • 这个表达式的结果是一个指向 member 成员的指针,这个指针的值就是 member 成员相对于 type 实例起始地址的偏移量。

4. (size_t)&(((type*)0)->member):

  • 最后,将上一步得到的指针强制转换为 size_t 类型。size_t 是一个无符号整数类型,通常用于表示大小和地址。
  • 这个表达式的结果是一个 size_t 类型的值,它表示 member 成员相对于 type 实例起始地址的偏移量。

注意!!!

这个宏的关键在于,它利用了C和C++中的一个未定义行为(undefined behavior):访问空指针。但是,编译器通常允许这种操作,并且会将其解释为获取成员的偏移量。这个宏在编译时计算偏移量,因此它不依赖于任何实际的内存分配。

详细解释

编译器的特殊处理

  1. 编译时: 编译器在处理 ((struct Point*)0)->x 这样的表达式时,会将其视为一个特殊的请求,用于计算成员的偏移量。编译器会根据结构体的布局计算出 x 成员的偏移量,并将这个值替换到表达式中。这是一个纯编译时的操作,不涉及实际的内存访问。

  2. 运行时: 在运行时,如果一个程序尝试通过一个空指针访问成员(例如,通过解引用一个空指针),这将导致未定义行为,通常是程序崩溃。然而,由于 ((struct Point*)0)->x 这样的表达式在编译时就已经被处理,所以实际上并不会在运行时发生这种未定义行为。

精彩评论(0)

0 0 举报