空指针的地址?

时间:2011-09-22 07:37:22

标签: c++ c

我遇到了下面的宏

#define OFFSETOF(TYPE, ELEMENT) ((size_t)&(((TYPE *)0)->ELEMENT))

我有点无法消化这个因为在c ++中,当我尝试使用空指针时,我会发现一个意外的行为......但是为什么它会有一个地址呢? null的地址是什么意思?

8 个答案:

答案 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的一些字节数。假设从ELEMENTTYPE *的实现定义转换表现得很直接(其他一些不能得到保证)通过语言),整个表达式的结果将成为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成员的偏移量。

如何尝试:

  • 使用空指针(代码中的值为0)
  • 获取元素(让编译器计算它的地址,从0开始)
  • 获取元素的地址(使用&
  • 将地址转换为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并获取元素的偏移量。

但是,我不确定这个结构是多么可移植。