我遇到了下面的宏
#define OFFSETOF(TYPE, ELEMENT) ((size_t)&(((TYPE *)0)->ELEMENT))
我有点无法消化这个因为在c ++中,当我尝试使用空指针时,我会发现一个意外的行为......但是为什么它会有一个地址呢? null的地址是什么意思?
答案 0 :(得分:11)
出于宏的目的:
它假定在地址0处存在类型为TYPE
的对象,并返回成员的地址,该成员实际上是结构中成员的偏移量。
This answer解释了为什么这是未定义的行为。我认为这是最重要的一句话:
如果
E1
的类型为“指向类X的指针”,则表达式E1->E2
将转换为等效形式(*(E1)).E2
;*(E1)
会导致 具有严格解释的未定义行为,.E2
转换它 到一个右值,使它成为弱者的未定义行为 解释
这是这种情况。虽然其他人认为这是有效的。重要的是要注意,这将在许多编译器上产生正确的结果。
答案 1 :(得分:8)
#define OFFSETOF(TYPE, ELEMENT) ((size_t)&(((TYPE *)0)->ELEMENT))
非常类似于offsetof()
(在C中)或<stddef.h>
(在C ++中)中定义的标准<cstddef>
宏的相当常见的定义。
0
是空指针常量。将其转换为TYPE *
会产生类型为TYPE *
的空指针。请注意,该语言不保证(或者甚至暗示)空指针的值为0,尽管它通常会这样做。
因此,(TYPE *)0
理论上是TYPE
类型的对象的地址,位于空指针指向的任何地址,((TYPE *)0)->ELEMENT))
是该对象的ELEMENT
成员
&
运算符获取此ELEMENT
成员的地址,并且强制转换将该地址转换为size_t
类型。
现在如果空指针恰好指向地址0,那么类型TYPE
对象的(不存在)对象从地址0开始,{{1}的地址该对象的成员位于一个地址,该地址偏离地址0的一些字节数。假设从ELEMENT
到TYPE *
的实现定义转换表现得很直接(其他一些不能得到保证)通过语言),整个表达式的结果将成为size_t
类型对象中ELEMENT
成员的偏移量。
所有这些都取决于几种未定义或未指定的行为。在大多数现代系统中,空指针实现为指向地址0的指针,地址(指针值)表示为整数,指定单片寻址空间内特定字节的索引,并从指针转换为整数相同大小的重新解释位。在具有此类特征的系统上,TYPE
宏可能有效,并且实现可以选择对标准OFFSETOF
宏使用类似的定义。 (代码实现的一部分可能会利用实现定义或未定义的行为;它不需要是可移植的。)
在没有这些特性的系统上,此offsetof
宏可能无效 - 并且实现必须使用其他方法来实现OFFSETOF
。这就是为什么offsetof
是标准库的一部分;它不能实现可移植性,但它总是可以用某种方式实现,适用于任何系统。一些实现使用编译器魔术,如gcc的offsetof
。
实际上,定义自己的__builtin_offsetof
宏没有多大意义,因为任何符合标准的C或C ++实现都会在其标准库中提供一个有效的OFFSETOF
宏。
答案 2 :(得分:6)
这不是取消引用指针,而是返回结构中元素的偏移量。
例如
typedef struct { char a; char b;} someStruct;
调用OFFSETOF(someStruct, b)
将返回1(假设其打包等)。
这与执行此操作相同:
someStruct str;
offset = (size_t)&(str.b) - (size_t)&str;
除了使用OFFSETOF
之外,您不需要创建虚拟变量。
当您需要找到类/ struct / union成员的偏移量时,无论出于何种原因,都需要这样做。
**编辑**
对于那些认为“标准不允许这样”的匆忙的选民 - 请再次阅读标准。在这种情况下,行为非常明确。
**另一个编辑**
我相信没有一个下注者注意到第一个参数是类型。我敢肯定,如果你认为比downvote需要的时间多一点,你就会理解你的错误。如果不是 - 那么,它不会是一群无知的挫败者压制正确答案的第一个。
答案 3 :(得分:4)
OFFSETOF
的目的是返回成员地址与其所属聚合地址之间的距离。
如果编译器没有根据其位置更改对象布局,则“距离”是常量,因此您开始的地址无关紧要。 0,在这种情况下,它只是一个像任何其他地址一样的地址。
根据C ++标准,访问无效地址是“未定义的行为”,但是:
如果这是编译器支持库的一部分(这是随VS2003推出的CRT中的“OFFSETOF”的实际代码!),那可能不是那么“未定义”(对于已知的编译器和平台,行为是支持库开发人员已知的:当然,这必须被视为“平台特定代码”,但不同的平台可能会有不同的库版本)
在任何情况下,你都没有对元素“行动”(所以没有“访问”完成),只是做一些普通的指针算法。 Thnk作为一般的缩小,如“如果在位置0处有一个对象,则其假定的ELEMENT成员将从位置6开始。因此6是偏移”。事实上,没有真正的这样的对象是无关紧要的。
顺便说一下,如果ELEMENT通过虚拟基础继承了TYEMENT,那么这个宏会失败(有分段错误!),因为,找到虚拟基地的位置您需要访问一些运行时信息 - 通常是对象v-table的一部分 - 其位置无法被检测到,因为对象地址不是“真实”地址。 这就是为什么该标准明确地说“取消引用无效指针是未定义的行为”。
下行:
我为平台特定的ansewr提供特定于平台的信息。 在downvote之前,请举例说明我所说的是假的。
答案 4 :(得分:4)
取消引用空指针(如此宏所做的)是未定义的行为。 你编写和使用这样的宏是不合法的,除非 实现为您提供了一些特殊的额外保证。
C标准库定义宏offsetof
;许多实现
使用类似的东西。实现可以做到这一点,因为
它知道编译器在这种情况下实际生成了什么,以及是否
它会导致问题。执行标准
图书馆可以使用很多你不能做的事情。
答案 5 :(得分:3)
一个。该操作有效,不会抛出任何异常,因为您没有尝试访问指针所指向的内存 B.空指针 - 它基本上是一个普通指针,表示对象位于地址0(根据定义,地址0是真实对象的无效地址),但指针自身有效。
所以这个宏是有意义的:如果TYPE类型的对象从地址0开始,那么他的ELEMENT将在内存中?换句话说,从ELEMENT到TYPE对象开始的偏移是什么。
答案 6 :(得分:3)
这是一个宏观的地狱,堆积了未定义的行为......
尝试做什么:获取struct
成员的偏移量。
如何尝试:
&
)size_t
有两个问题:
size_t
并不是应该做的事情(问题是指针不能保证适合)如何做到:
在代码中:
#define OFFSETOF(Object, Member) \
((diffptr_t)((char*)(&Object.Member) - (char*)(&Object))
但是它需要一个物体,因此可能不适合您的目的。
如何完成:
#include <cstddef>
#define OFFSETOF(Struct, Member) offsetof(Struct, Member)
但是没有什么意义......对吧?
对于好奇,定义可以是:__builtin_offsetof(st, m)
(来自Wikipedia)。一些编译器使用null解引用实现它,但是它们 编译器,因此知道他们安全地处理这种情况;这不是可移植的...并且不必在切换编译器之后,也可以切换C库实现。
答案 7 :(得分:2)
littleadv的构造意图恰到好处。稍微解释一下:您转换了一个指向地址0x0的结构指针并对其元素进行解引用。您指向的地址现在为0x0 +元素具有的任何偏移量。现在将此值转换为size_t并获取元素的偏移量。
但是,我不确定这个结构是多么可移植。