【问题标题】:UB when manipulating nullptr [duplicate]UB 操作 nullptr [重复]
【发布时间】:2015-05-20 12:48:22
【问题描述】:

这是围绕Example of error caused by UB of incrementing a NULL pointer的讨论的相关问题

假设我定义了这个数据结构:

union UPtrMem
{
    void* p;
    char ach[sizeof(void*)];
}

UPtrMem u;
u.p = nullptr;
u.p++;      // UB according to standards
u.ach[0]++; // why is this OK then??

pach 共享相同的内存,那么仅仅是修改内存位置(恰好包含指针)UB 的行为吗?我认为只有在您尝试取消引用指针时它才会变得未定义。

【问题讨论】:

  • 我不明白这个问题与您链接的问题有何不同(以及与该问题相关的问题)。当一个程序包含 UB 时,它包含 UB。如该问题中所述,您的程序故意在u.p++ 包含UB。还有什么好说的?
  • AFAIK,使用char 数组对指针进行别名处理并使用char 数组执行某些操作不能保证在您再次将内存解释为指针时产生任何合理的效果。
  • 你基本上把this questionthis question放在同一个问题上。我很想把这个问题作为至少一个重复的问题来结束。有什么理由不应该?
  • @Jefffery:我认为 OP 隐含了他问题的关键部分:我怀疑他实际上是想问“u.ach[0]++u.p++ 做同样的事情,那么为什么是一个 UB 和另一个没有?”。
  • −1 不是真正的代码,而且显然是为诱捕响应者而设计的。

标签: c++ pointers


【解决方案1】:

这仍然是 UB,因为

从最近未写入的联合成员中读取是未定义的行为。

(来自here)。所以你有 UB,不管p 的值是多少。总结:

那为什么会这样呢??

不是。

【讨论】:

  • 您必须更改代码(不仅是缺少的分号,还包括指针的类型)才能使其编译。因此,“这个”是 UB 是不正确的。你可能会说如果代码以这样那样的方式改变,那么改变的代码将编译并展示UB,但仅此而已。也就是说,添加了分号并将指针更改为char*,代码只是形式上的UB,没有问题,因为指针至少是一个字节(即与char重叠),并且因为显然没有现存的C++实现有一个不是位模式全零的空指针值。
  • @Cheersandhth.-Alf 我将问题解释为“为什么它不能执行第一个增量,而是执行第二个增量”。显然,这并没有完全关闭,因为 OP 接受了答案。我解释说他也不能进行第二次增量,“无论p 的值如何”。我意识到缺少分号,但觉得这不是问题的重点。正式的 UB 仍然是 UB。
【解决方案2】:

您的示例不包含任何 UB,因为您还没有做到这一点:它是无法编译的无效代码。

要拥有你所想的那种 UB,标题的“UB when manipulating nullptr”,你需要让代码执行

不编译时不会发生这种情况。


以防万一我回答后问题发生了变化,这在这些明显设计为诱捕响应者的问题中并不少见,这是我在写这篇文章时呈现的代码:

union UPtrMem
{
    void* p;
    char ach[sizeof(void*)];
}

UPtrMem u;
u.p = nullptr;
u.p++;      // UB according to standards
u.ach[0]++; // why is this OK then??

增加void* 是无效的,不是受支持的操作,并且不会编译。

【讨论】:

  • 我只想补充一点,如果 OP 使用它(如果你在联合之后添加缺少的分号),它实际上是在 gcc 上编译的。 void * 上的算术基本上被视为 char *。不过,它会明确警告 UB:example
  • @RaphaelMiedl:-pedantic-errors 选项解决了这个问题。 ;-)
  • 我知道它不能编译。我注释掉了 u.p++;看看会发生什么(也)只是为了确认。
  • 诚挚道歉,无意陷害任何人,我不会改变任何事情。
【解决方案3】:

标准使null 指针递增的原因未定义 是因为null 指针并不总是包含像0 这样的算术上有意义的值。它可能包含一个特定的位模式,指示CPU 的不可寻址内存。

你的例子也有其他问题。

当你增加一个分配的指针时,它会将它指向的东西的大小添加到它的值。

因此,在32bit 计算机上,int* 可能会在您向其添加1 时将4 位置(sizeof(int))。

void* 的问题是编译器没有大小信息,因此无法知道将其值增加多远。

你的示例中,你可以这样做:

u.ach[0]++;

这根本不会增加指针,它会增加 char 数组的第一个元素中包含的 char 值。当然,这是未定义的,因此即使它有效,您也不能依赖它具有任何特定价值。

【讨论】:

  • 这都是正确的,但与问题无关。问题是关于递增空指针的不确定性,而不是关于空指针算术的有效性。
  • @molbdnilo 你说得对,应该更仔细地阅读这个问题。
【解决方案4】:

在我看来u.p++; 甚至无效,因为 void 没有大小,所以 - 没有什么可以增加的。但是u.ach[0]++; 是有效的,因为您增加了一个字符。

编辑是的,它占用了结构中的空间...但是它指向的内容没有大小...它会增加多少?

【讨论】:

  • 感谢投反对票...请教育我?
  • 不是我的反对票,而是 1)我们在这里谈论的是 void *,它有一个大小,2)面对鲍姆的回答,你和我的答案都没有实际意义—— u.ach[0] 在这种情况下是未定义的行为。
  • 增加指向 char 的指针会增加 1。增加指向结构的指针会增加结构的大小。增加一个 void 是如何工作的?
  • void 指针上的指针算术是完全非法的,因为就像你自己说的“void 没有大小”。 gcc 允许它作为扩展并以与char * 相同的方式处理它,请参阅:Pointer arithmetic for void pointer in C。同样增加nullptr 是UB,参见:Is incrementing a null pointer well-defined?
  • 仅供参考:甚至无法在 MSVC++2013 中编译...
猜你喜欢
  • 1970-01-01
  • 2016-12-01
  • 2012-03-03
  • 2017-03-31
  • 1970-01-01
  • 1970-01-01
  • 2019-01-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多