【发布时间】:2010-09-28 20:16:39
【问题描述】:
__builtin_offsetof 运算符(或 Symbian 中的 _FOFF 运算符)在 C++ 中的用途是什么?
此外它还返回什么?指针?字节数?
【问题讨论】:
__builtin_offsetof 运算符(或 Symbian 中的 _FOFF 运算符)在 C++ 中的用途是什么?
此外它还返回什么?指针?字节数?
【问题讨论】:
它是 GCC 编译器提供的内置函数,用于实现 C 和 C++ 标准指定的 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 指针本身的列表),但遗憾的是,我不记得它在哪里有助于实现它。
【讨论】:
正如@litb 所说:结构/类成员的字节偏移量。在 C++ 中,有些情况下它是未定义的,以防编译器抱怨。 IIRC,实现它的一种方法(至少在 C 中)是做
#define offsetof(type, member) (int)(&((type *)0)->member)
但我确信这有问题,但我会留给感兴趣的读者指出......
【讨论】:
正如@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 的哈希表。顺便说一句,这个例子真是太棒了。
内置__offsetof 运算符的目的是编译器供应商可以继续#define 一个offsetof() 宏,但让它与定义一元operator& 的类一起工作。 offsetof() 的典型 C 宏定义仅在 (&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
【讨论】:
& 返回一个指针,它通常不接受一个作为参数。参见示例,& 应用于 foo,这是一个对象。
&foo->bar 将返回指向 bar 的指针。在 C++ 中,这会将& operator 应用于bar 而不是foo。此外,在offsetof 的定义中写成(type*),这意味着0 被强制转换为指针而不是类,因此CFoo 的运算符& 在任何情况下都不能应用。
CFoo 不同,CFoo* 是一个内置类型(即指针)。另外,即使在 C++ 中,语法规则(检查你的书)也会忽略重载。运算符优先级是固定的。这意味着为了 C 兼容性&foo->bar 必须在有和没有运算符重载的情况下解析相同的内容。