代码之家  ›  专栏  ›  技术社区  ›  chappar

offsetof()的这个实现为什么有效?

  •  32
  • chappar  · 技术社区  · 17 年前

    #define offsetof(st, m) \
        ((size_t) ( (char *)&((st *)(0))->m - (char *)0 ))
    

    7 回复  |  直到 11 年前
        1
  •  39
  •   Jason Aller    11 年前

    * 或 -> 对地址值使用,以查找引用的值。唯一的用途 上面是一个类型声明,用于强制转换。

    这个 运算符在上面使用,但不用于访问值。相反,它用于获取值的地址。这是一个非宏代码示例,应该可以让它更清晰一些

    SomeType *pSomeType = GetTheValue();
    int* pMember = &(pSomeType->SomeIntMember);
    

    第二行实际上不会导致解引用(依赖于实现)。它只返回 SomeIntMember 在 pSomeType 价值

    您看到的是任意类型和字符指针之间的大量转换。使用char的原因是,它是C89标准中唯一(可能是唯一)具有显式大小的类型之一。尺寸是1。通过确保大小为1,上述代码可以实现计算值的真实偏移量的邪恶魔法。

        2
  •  10
  •   Jonathan Leffler    10 年前

    offsetof ,该标准没有强制要求,只是说:

    <stddef.h> [...]

    offsetof( type , member-designator )

    它扩展为具有类型的整型常量表达式 size_t 它是结构成员(由指定)的偏移量(以字节为单位) 成员指示符 ), 从其结构开始(由 类型 ).类型和成员标识符 应确保

    static 类型 t;

    &(t. 成员指示符 )

    阅读P J Plauger的“标准C库”,了解关于它和中其他项目的讨论 <标准差速> 哪些边界线功能可以(应该?)使用正确的语言,并且可能需要特殊的编译器支持。

    它只具有历史意义,但我在386/IX上使用了早期的ANSI C编译器(参见,我告诉过你历史意义,大约在1990年),该编译器在该版本上崩溃 抵消

    #define offsetof(st, m) ((size_t)((char *)&((st *)(1024))->m - (char *)1024))
    

    这是某种编译器错误,尤其是因为头是与编译器一起分发的,无法工作。

        3
  •  9
  •   MSalters    17 年前

    在ANSI C中, offsetof 不是这样定义的。它没有这样定义的原因之一是某些环境确实会抛出空指针异常,或者以其他方式崩溃。因此,ANSIC保留了 offsetof( ) 对编译器生成器开放。

    上面显示的代码通常适用于不主动检查空指针的编译器/环境,但仅当从空指针读取字节时才会失败。

        4
  •  8
  •   sigjuice    17 年前

    回答问题的最后一部分,代码是不可移植的。

        5
  •  2
  •   chaos    17 年前

    它不会出错,因为你没有去引用它。指针地址用作从另一个数字中减去的数字,而不是用于寻址内存操作。

        6
  •  2
  •   Sean Bright Sean Stinehour    17 年前

    它计算成员的偏移量 m st

    ((st *)(0)) 指 NULL 类型的指针 st * &((st *)(0))->m 指此对象中成员m的地址。因为这个对象的起始地址是 0 (NULL)

    char * 转换和差值以字节为单位计算偏移量。根据指针操作,当您在类型为 T * ,结果是类型为的对象数 T 在操作数包含的两个地址之间表示。

        7
  •  2
  •   Jonathan Leffler    10 年前

    清单1:一组具有代表性的 offsetof() 宏定义

    // Keil 8051 compiler
    #define offsetof(s,m) (size_t)&(((s *)0)->m)
    
    // Microsoft x86 compiler (version 7)
    #define offsetof(s,m) (size_t)(unsigned long)&(((s *)0)->m)
    
    // Diab Coldfire compiler
    #define offsetof(s,memb) ((size_t)((char *)&((s *)0)->memb-(char *)0))
    
    typedef struct 
    {
        int     i;
        float   f;
        char    c;
    } SFOO;
    
    int main(void)
    {
      printf("Offset of 'f' is %zu\n", offsetof(SFOO, f));
    }
    

    宏中的各种运算符的求值顺序如下:

    1. ((s *)0) 接受整数零并将其强制转换为指向的指针 s
    2. ((s *)0)->m 解除指向结构成员的指针的引用 m
    3. &(((s *)0)->m) 计算的地址 M .
    4. (size_t)&(((s *)0)->m)

        8
  •  0
  •   explogx    6 年前

    offsetof 宏:

    C标准第6.6节第9段

    地址常量是空指针、指向指定静态存储持续时间对象的左值的指针或指向函数指示符的指针;它应使用一元数显式创建 & 运算符或整型常量转换为指针类型,或通过使用数组或函数类型的表达式隐式转换。数组下标 [] . 和 -> 接线员,地址 & 和间接 * 一元运算符和指针强制转换可用于创建地址常量,但不能使用这些运算符访问对象的值。

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

    表达式包括地址常量的创建。

    另外,考虑C标准中的这个报价:

    类型和构件代号应符合以下要求:

    static type t;

    然后是表情 &(t.member-designator) 计算为地址常量。(如果 指定的成员是位字段,行为未定义。)

    C中的结构是一种复合数据类型(或记录)声明,它定义了内存块中一个名称下的物理分组变量列表,允许通过单个指针或返回相同地址的结构声明名称访问不同的变量。

    从编译器的角度来看,结构声明的名称是一个地址,成员指示符是该地址的偏移量。