空指针的地址

Address of Null pointer?

本文关键字:地址 空指针      更新时间:2023-10-16

我遇到了下面的宏

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

我有点无法消化这一点,因为在c++中,当我尝试遵循空指针时,我预计会出现意外行为…但它怎么会有地址呢?空地址是什么意思?

对于宏的目的:它假设在地址0处存在一个TYPE类型的对象,并返回成员的地址,该地址实际上是该成员在结构中的偏移量。

这个答案解释了为什么这是未定义行为。我认为这是最重要的一句话:

如果E1的类型是"指向类X的指针",则表达式E1->E2转换为等价形式(*(E1)).E2;*(E1)将导致使用严格解释的未定义行为,.E2将其转换到一个右值,使其成为弱类型的未定义行为解释。

就是这里的情况。尽管其他人认为这是有效的。值得注意的是,这将在许多编译器上产生正确的结果。

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

非常类似于标准offsetof()宏的一个相当常见的定义,在<stddef.h> (C)或<cstddef> (c++)中定义。

0是一个空指针常量。将其转换为TYPE *会产生一个类型为TYPE *空指针。请注意,该语言不保证(甚至暗示)空指针的值为0,尽管它通常是这样做的。

因此,(TYPE *)0在理论上是TYPE类型对象的地址,位于空指针指向的任何地址,((TYPE *)0)->ELEMENT))是该对象的ELEMENT成员。

&操作符接受ELEMENT成员的地址,强制类型转换将该地址转换为size_t类型。

现在如果一个空指针恰好指向地址0,那么(不存在的)TYPE对象类型的对象从地址0开始,并且该对象的ELEMENT成员的地址位于地址0偏移的某个字节数的地址。假设实现定义的从TYPE *size_t的转换以一种直接的方式进行(语言不能保证的其他方式),则整个表达式的结果将是TYPE类型对象中ELEMENT成员的偏移量。

所有这些都取决于几个未定义或未指定的行为。在大多数现代系统中,空指针被实现为指向地址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,你不需要创建一个虚拟变量。

当您出于任何原因需要查找类/结构/联合成员的偏移量时,

**编辑

对于那些认为"标准不允许这样做"的人,请再阅读一遍标准。在这种情况下,行为定义得非常好。

**另一个编辑**

我相信没有人注意到第一个参数是类型。我敢肯定,如果你再多想一点,你就会明白你的错误。如果没有——好吧,这也不是第一次有一群无知的看跌者压制正确答案了。

解除空指针的引用(如这个宏所做的)是未定义的行为。编写和使用这样的宏是不合法的,除非实现为您提供了一些特殊的,额外的保证。

C标准库定义了一个宏offsetof;很多实现一定要使用类似的方法。实现可以这样做,因为它知道编译器在这种情况下实际生成了什么,以及是否它会不会引起问题。标准的实施图书馆可以使用很多你不能使用的东西。

OFFSETOF的目的是返回成员的地址与其所属聚合的地址之间的距离。

如果编译器没有根据对象的位置改变对象的布局,那么"距离"是恒定的,因此您的起始地址是无关紧要的。在这种情况下,它只是一个地址。

根据c++标准,访问一个无效的地址是"未定义的行为",但是:

  • 如果这是编译器支持库的一部分(这是VS2003自带的CRT中的"OFFSETOF"的实际代码!),那可能不是那么"未定义"(对于已知的编译器和平台,该行为为支持库开发人员所知:当然,这必须被认为是"平台特定代码",但不同的平台可能有不同的库版本)

  • 在任何情况下,您都没有"操作"元素(因此没有"访问"完成),只是做一些简单的指针算术。就像"如果在位置0有一个对象,它假定的ELEMENT成员将从位置6开始。因此6是偏移量"。事实上,没有真实的这样的对象是无关紧要的。

  • 顺便说一下,如果ELEMENT通过虚拟基由TYPE继承,则该宏会失败(带有分割错误!),因为要定位虚拟基的位置,您需要访问一些运行时信息-通常是对象v表的一部分-其位置无法检测到,因为对象地址不是"真实"地址。这就是为什么标准谨慎地说"解引用无效指针是未定义的行为"。

DOWNVOTERS

:

我为特定于平台的答案提供特定于平台的信息。在拒绝投票之前,请提供一个证明,我说的是假的。

该操作是有效的,不会抛出异常,因为您没有尝试访问指针所指向的内存。
B.空指针-它基本上是一个普通的指针,表示对象位于地址0(地址0的定义是一个无效的地址为实际对象),但指针是自我有效的。

所以这个宏的意思是:如果type类型的对象从地址0开始,他的ELEMENT将在内存中的哪个位置?换句话说,从ELEMENT到TYPE对象开始的偏移量是多少。

这是一个该死的宏,堆积未定义行为…

它试图做的是:获取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)(来自维基百科)。一些编译器用null解引用来实现它,但是它们是编译器,因此知道它们安全地处理这种情况;这是不便携的…而且不必既然切换编译器,还可以切换C库实现。

littleleadv的意图是正确的。稍微解释一下:强制转换一个指向地址0x0的结构指针,并对它的一个元素解引用。您指向的地址现在是0x0 +元素的偏移量。现在将该值强制转换为size_t类型,并获得元素的偏移量。

我不确定这个构造的可移植性如何。