【问题标题】:what is the purpose and return type of the __builtin_offsetof operator?__builtin_offsetof 运算符的用途和返回类型是什么?
【发布时间】:2010-09-28 20:16:39
【问题描述】:

__builtin_offsetof 运算符(或 Symbian 中的 _FOFF 运算符)在 C++ 中的用途是什么?

此外它还返回什么?指针?字节数?

【问题讨论】:

    标签: c++ symbian


    【解决方案1】:

    它是 GCC 编译器提供的内置函数,用于实现 C 和 C++ 标准指定的 offsetof 宏:

    GCC - offsetof

    它返回 POD 结构/联合成员所在的字节偏移量。

    示例:

    struct abc1 { int a, b, c; };
    union abc2 { int a, b, c; };
    struct abc3 { abc3() { } int a, b, c; }; // non-POD
    union abc4 { abc4() { } int a, b, c; };  // non-POD
    
    assert(offsetof(abc1, a) == 0); // always, because there's no padding before a.
    assert(offsetof(abc1, b) == 4); // here, on my system
    assert(offsetof(abc2, a) == offsetof(abc2, b)); // (members overlap)
    assert(offsetof(abc3, c) == 8); // undefined behavior. GCC outputs warnings
    assert(offsetof(abc4, a) == 0); // undefined behavior. GCC outputs warnings
    

    @Jonathan 提供了一个很好的例子来说明你可以在哪里使用它。我记得曾经见过它用于实现侵入式列表(其数据项包括 next 和 prev 指针本身的列表),但遗憾的是,我不记得它在哪里有助于实现它。

    【讨论】:

    • 我猜它有用的地方是被侵入的节点包含指向“下一个”对象中节点的指针。使用列表时,需要从节点到对象的基部,所以从指针值中减去offsetof(something)字节,然后重新解释_cast。
    • 当然,所有在 C++ 中都非常不可移植,但在 C 中可以完成这项工作。
    【解决方案2】:

    正如@litb 所说:结构/类成员的字节偏移量。在 C++ 中,有些情况下它是未定义的,以防编译器抱怨。 IIRC,实现它的一种方法(至少在 C 中)是做

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

    但我确信这有问题,但我会留给感兴趣的读者指出......

    【讨论】:

    • 未定义的行为,即使在 C 中也是如此。多种原因,甚至:重新定义 std 宏并取消引用 NULL。但在 stdlib 中很常见,因为它受不同规则的约束。
    • @MSalters - JesperE 是正确的。参见Linux Kernel源码中stddef.h中的定义:lxr.linux.no/#linux+v2.6.31/include/linux/stddef.h#L24
    【解决方案3】:

    正如@litb 指出和@JesperE 所示,offsetof() 以字节为单位提供整数偏移量(作为size_t 值)。

    什么时候可以使用?

    可能相关的一种情况是表驱动操作,用于从文件中读取大量不同的配置参数并将值填充到同样庞大的数据结构中。将巨大减少到如此微不足道(并忽略各种必要的实际实践,例如在标题中定义结构类型),我的意思是一些参数可能是整数和其他字符串,并且代码可能看起来像:

    #include <stddef.h>
    
    typedef stuct config_info config_info;
    struct config_info
    {
       int parameter1;
       int parameter2;
       int parameter3;
       char *string1;
       char *string2;
       char *string3;
       int parameter4;
    } main_configuration;
    
    typedef struct config_desc config_desc;
    static const struct config_desc
    {
       char *name;
       enum paramtype { PT_INT, PT_STR } type;
       size_t offset;
       int   min_val;
       int   max_val;
       int   max_len;
    } desc_configuration[] =
    {
        { "GIZMOTRON_RATING", PT_INT, offsetof(config_info, parameter1), 0, 100, 0 },
        { "NECROSIS_FACTOR",  PT_INT, offsetof(config_info, parameter2), -20, +20, 0 },
        { "GILLYWEED_LEAVES", PT_INT, offsetof(config_info, parameter3), 1, 3, 0 },
        { "INFLATION_FACTOR", PT_INT, offsetof(config_info, parameter4), 1000, 10000, 0 },
        { "EXTRA_CONFIG",     PT_STR, offsetof(config_info, string1), 0, 0, 64 },
        { "USER_NAME",        PT_STR, offsetof(config_info, string2), 0, 0, 16 },
        { "GIZMOTRON_LABEL",  PT_STR, offsetof(config_info, string3), 0, 0, 32 },
    };
    

    您现在可以编写一个通用函数,从配置文件中读取行,丢弃 cmets 和空行。然后它隔离参数名称,并在 desc_configuration 表中查找它(您可以对其进行排序以便进行二分搜索 - 多个 SO 问题解决了这个问题)。当它找到正确的config_desc 记录时,它可以将找到的值和config_desc 条目传递给两个例程之一——一个用于处理字符串,另一个用于处理整数。

    这些功能的关键部分是:

    static int validate_set_int_config(const config_desc *desc, char *value)
    {
        int *data = (int *)((char *)&main_configuration + desc->offset);
        ...
        *data = atoi(value);
        ...
    }
    
    static int validate_set_str_config(const config_desc *desc, char *value)
    {
        char **data = (char **)((char *)&main_configuration + desc->offset);
        ...
        *data = strdup(value);
        ...
    }
    

    这避免了必须为结构的每个单独成员编写单独的函数。

    【讨论】:

    • 如果你想变得非常邪恶,你可以使用包含参数名称和索引到desc_configuration 的哈希表。顺便说一句,这个例子真是太棒了。
    • @Robert:这个例子非常接近于从配置文件中读取数据到大数据结构中,然后逆向处理。我不会费心解释它目前是如何完成的:我只想说,有 300 个参数,处理这一切的函数中有大约 4500 行代码,并且有很多重复。我不负责代码 - 很遗憾。
    【解决方案4】:

    内置__offsetof 运算符的目的是编译器供应商可以继续#define 一个offsetof() 宏,但让它与定义一元operator&amp; 的类一起工作。 offsetof() 的典型 C 宏定义仅在 (&amp;lvalue) 返回该右值的地址时才有效。 IE。

    #define offsetof(type, member) (int)(&((type *)0)->member) // C definition, not C++
    struct CFoo {
        struct Evil {
            int operator&() { return 42; }
        };
        Evil foo;
    };
    ptrdiff_t t = offsetof(CFoo, foo); // Would call Evil::operator& and return 42
    

    【讨论】:

    • 这很奇怪。运算符 & 是为 CFoo 类定义的,而不是为指向此类的指针定义的。因此不清楚为什么会调用它。其次, -> 具有更高的优先级,并且 & 将适用于“成员”。显然 GC++ 编译器中存在错误,他们用丑陋的 __builtin 掩盖了它,它永远卡在 GCC 中。
    • @Noname: &amp; 返回一个指针,它通常不接受一个作为参数。参见示例,&amp; 应用于 foo,这是一个对象。
    • 首先,“通常”不适用于 C++,在 C++ 中你可以用你想要的任何东西重载任何运算符。但无论如何,& 运算符与其他运算符相比具有优先级。就是这样,在 C 中 &amp;foo-&gt;bar 将返回指向 bar 的指针。在 C++ 中,这会将&amp; operator 应用于bar 而不是foo。此外,在offsetof 的定义中写成(type*),这意味着0 被强制转换为指针而不是类,因此CFoo 的运算符& 在任何情况下都不能应用。
    • @Noname:不正确。你不能重载“任何你想要的任何运算符”,至少对于内置类型是这样。并且与CFoo 不同,CFoo* 是一个内置类型(即指针)。另外,即使在 C++ 中,语法规则(检查你的书)也会忽略重载。运算符优先级是固定的。这意味着为了 C 兼容性&amp;foo-&gt;bar 必须在有和没有运算符重载的情况下解析相同的内容。
    • 好的,我知道会发生什么。您将示例写得如此糟糕和令人困惑,以至于我认为运算符 & 是为 CFoo 定义的,而实际上它是为包含的类 Evil 定义的。但是由于某种原因,您决定将该成员命名为“foo”。无论如何,我所说的仍然成立,运算符 & 适用于“成员”而不适用于 ((type*)0)。
    猜你喜欢
    • 1970-01-01
    • 2013-11-21
    • 2017-03-01
    • 1970-01-01
    • 2019-02-04
    • 2018-03-09
    • 1970-01-01
    相关资源
    最近更新 更多