空指针的地址?

我在下面看到了这个宏

#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并获取元素的偏移量。

不过,我不确定这个结构是多么便携。