【问题标题】:Can value of buffer change after destructor executes析构函数执行后缓冲区的值是否可以更改
【发布时间】:2024-01-12 12:01:02
【问题描述】:

代码:

#include <cstdio>
#include <new>

struct Foo {
  char ch;
  ~Foo() { ++ch; }
};

int main() {
  static_assert(sizeof(Foo) == 1);
  char buffer;
  auto const* pc = new (&buffer) Foo{42};
  
  // Change value using only the const pointer
  std::printf("%d\n", +buffer);
  pc->~Foo();
  std::printf("%d\n", +buffer);
}

godbolt

据我所知,我没有引起任何 UB,但 GCC 和 Clang 对结果存在分歧。我认为输出显然应该是“42 43”。 Clang 就是这种情况,但 GCC 认为输出是“42 0”。这怎么可能?谁将缓冲区归零?我错过了什么吗?

【问题讨论】:

  • 你的代码是未定义的行为,所以编译器都没有错。
  • @BenVoigt 你能详细说明什么是未定义的吗?
  • 你的期望是错误的。编译器发现 ++ch 的结果未被使用并忽略增量。您可以使用volatile char ch; 更改行为。
  • @S.M.那么它会打印 42 42?
  • @Eljay 我的问题是我经常将char 视为内存字节,而不是一个拥有自己生命周期的适当对象。

标签: c++ undefined-behavior object-lifetime placement-new


【解决方案1】:

您的代码具有未定义的行为。 buffer 的存储空间已被您创建的 Foo 对象重用,因此它的生命周期已结束,您无法再使用它。该标准的相关部分是[basic.life]/1,其中 1.5 是相关的子部分。

类型 T 的对象 o 的生命周期在以下情况下结束:[...]

  • 对象占用的存储空间被释放,或者被未嵌套在 o ([intro.object]) 中的对象重用。

【讨论】:

  • 有趣。如果它是通过 malloc 分配的呢? char* buffer = (char*)malloc(1):?
  • @AyxanHaqverdili 仍然是 UB,因为指针指向的 char 对象也将重新使用它的存储空间。
  • @AyxanHaqverdili:同样的问题。请注意,您可以合法地将 char 值存储到 buffer(迂腐地通过 new (&amp;buffer) char(x)),因为您仍然有有效的存储空间。它只是在杀死 UB 对象后读取它。
【解决方案2】:

在最后一行中,左值 buffer 不访问任何对象。

最初存在的char 对象的生命周期通过将其存储重新用于Foo 而结束。 Foo 通过调用析构函数结束了它的生命周期。之后没有人在存储中创建任何对象。

在不存在对象的情况下,不允许进行左值到右值的转换(+buffer 所做的,但也将 buffer 作为参数传递给可变参数函数)。

【讨论】:

    【解决方案3】:
    §6.7.3.5
    A program may end the lifetime of any object by reusing the
    storage which the object occupies ...[cut]
    

    您在buffer 的生命周期到期后访问它。

    【讨论】: