空指针的地址?
我在下面看到了这个宏
#define OFFSETOF(TYPE, ELEMENT) ((size_t)&(((TYPE *)0)->ELEMENT))
我有点无法消化这个,因为在c ++中,当我尝试使用空指针时,我期待一个意外的行为……但是为什么它会有一个地址呢? null的地址是什么意思?
出于宏的目的:它假定在地址0处存在TYPE
的对象并返回成员的地址,该成员实际上是结构中成员的偏移量。
这个答案解释了为什么这是未定义的行为。 我认为这是最重要的一句话:
如果
E1
具有“指向类X的指针”类型,则表达式E1->E2
被转换为等效forms(*(E1)).E2
;*(E1)
将导致具有严格解释的未定义行为,并且.E2
将其转换为rvalue,使其成为弱解释的未定义行为。
这是这种情况。 虽然其他人认为这是有效的。 重要的是要注意,这将在许多编译器上产生正确的结果。
#define OFFSETOF(TYPE, ELEMENT) ((size_t)&(((TYPE *)0)->ELEMENT))
非常类似于标准的offsetof()
宏的相当常见的定义,在
(在C中)或
(在C ++中)中定义。
0
是空指针常量 。 将其TYPE *
转换为TYPE *
产生TYPE *
的空指针 。 请注意,该语言不保证(或甚至暗示)空指针的值为0,尽管它通常是这样。
因此, (TYPE *)0
理论上是TYPE
的对象的地址,位于空指针指向的任何地址, ((TYPE *)0)->ELEMENT))
是该对象的ELEMENT
成员。
&
运算符获取此ELEMENT
成员的地址,并且强制转换将该地址转换为size_t
类型。
现在, 如果空指针恰好指向地址0,那么TYPE
对象类型的(不存在的)对象从地址0开始,该对象的ELEMENT
成员的地址位于从地址偏移一定数量字节的地址0.假设从TYPE *
到size_t
的实现定义转换行为方式简单(语言无法保证的其他内容),整个表达式的结果将成为ELEMENT
成员在一个对象中的偏移量。键入TYPE
。
所有这些都取决于几种未定义或未指定的行为。 在大多数现代系统中,空指针实现为指向地址0的指针,地址(指针值)表示为整数,指定单片寻址空间内特定字节的索引,并从指针转换为整数相同大小的重新解释位。 在具有这种特性的系统上, OFFSETOF
宏可能有效,并且实现可以选择对标准offsetof
宏使用类似的定义。 (代码实现的一部分可能会利用实现定义或未定义的行为;它不需要是可移植的。)
在没有这些特性的系统上,此OFFSETOF
宏可能无法工作 – 并且实现必须使用其他一些方法来实现offsetof
。 这就是为什么offsetof
是标准库的一部分; 它不能实现可移植性,但它总是可以以某种方式为任何系统实现。 有些实现使用编译器魔术,比如gcc的__builtin_offsetof
。
实际上,定义自己的OFFSETOF
宏没有多大意义,因为任何符合要求的C或C ++实现都会在其标准库中提供一个工作的offsetof
。
这不是取消引用指针,而是返回结构中元素的偏移量。
例如
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需要的时间多一点,你就会理解你的错误。 如果不是 – 那么,它不会是第一批无知的挫败者压制正确答案的第一个。
取消引用空指针(如此宏所做的)是未定义的行为。 您编写和使用此类宏不合法,除非该实现为您提供了一些特殊的额外保证。
C标准库定义了一个宏offsetof
; 许多实现都使用类似的东西。 实现可以这样做,因为它知道编译器在这种情况下实际生成了什么,以及它是否会导致问题。 标准库的实现可以使用很多你不能做的事情。
OFFSETOF
的目的是返回成员地址与其所属聚合地址之间的距离。
如果编译器没有根据其位置更改对象布局,则“距离”是常量,因此您开始的地址无关紧要。 0,在这种情况下,它只是一个像任何其他地址。
根据C ++标准,访问无效地址是“未定义的行为”,但是:
-
如果这是编译器支持库的一部分(这是随VS2003一起提供的CRT中的“OFFSETOF”的实际代码!),那可能不是那么“未定义”(对于已知的编译器和平台,支持已知该行为)库开发人员:当然,这必须被视为“平台特定代码”,但不同的平台可能会有不同的库版本)
-
在任何情况下,你都没有对元素“行动”(所以没有“访问”完成),只是做一些普通的指针算法。 Thnk作为一般的例外, 如果 “ 如果在位置0处有一个物体,则其假定的ELEMENT成员将从位置6开始。因此6是偏移量 ”。 事实上,没有真正的这样的对象是无关紧要的。
-
顺便说一下,如果ELEMENT通过虚拟基础inheritance了ELEMENT,那么这个宏会失败(带有分段错误!),因为要找到虚拟基础的位置,你需要访问一些运行时信息 – 通常是对象的一部分v-table-其位置无法被检测到,是对象地址而不是“真实”地址。 这就是为什么标准认为“解除引用无效指针是未定义的行为”的原因。
下行:
我为平台特定的ansewr提供特定于平台的信息。 在downvote之前,请提供一个certificate,我说的是假的。
答:该操作有效,不会抛出任何exception,因为您没有尝试访问指针指向的内存。
B.空指针 – 它基本上是一个普通指针,表示对象位于地址0(根据定义,地址0是真实对象的无效地址),但指针自身有效。
所以这个宏意味着:如果TYPE类型的对象从地址0开始,那么他的ELEMENT将在内存中? 换句话说,从ELEMENT到TYPE对象开始的偏移是什么。
这是一个宏观的地狱,堆积未定义的行为……
它试图做什么:获取struct
成员的偏移量。
它是如何尝试的:
- 使用空指针(代码中的值为0)
- 获取元素(让编译器计算它的地址,从0开始)
- 获取元素的地址(使用
&
) - 将地址转换为
size_t
有两个问题:
- 取消引用空指针是未定义的行为,因此技术上任何事情都可能发生
- 将指针
size_t
转换为size_t
不是应该做的事情(问题是指针不能保证适合)
如何做到:
- 使用真实物体
- 计算地址的差异
在代码中:
#define OFFSETOF(Object, Member) \ ((diffptr_t)((char*)(&Object.Member) - (char*)(&Object))
但是它需要一个物体,因此可能不适合您的目的。
该怎么做 :
#include #define OFFSETOF(Struct, Member) offsetof(Struct, Member)
但是没有什么意义……对吗?
对于好奇,定义可以是: __builtin_offsetof(st, m)
(来自维基百科 )。 有些编译器使用null dereferences实现它,但它们是编译器,因此知道它们安全地处理这种情况; 这不是可移植的……并且不需要自切换编译器以来,您也可以切换C库实现。
littleadv的构造意图恰到好处。 稍微解释一下:您转换了一个指向地址0x0的结构指针并对其元素进行了解引用。 您指向的地址现在为0x0 +元素具有的任何偏移量。 现在将此值转换为size_t并获取元素的偏移量。
不过,我不确定这个结构是多么便携。