【问题标题】:Is the usage of reinterpret_cast on a memcpy buffer UB?在 memcpy 缓冲区 UB 上使用 reinterpret_cast 吗?
【发布时间】:2020-01-13 12:00:46
【问题描述】:

给定代码

struct A {};

auto obj = new A;
std::vector<unsigned char> buffer;
buffer.resize(sizeof(obj));
std::memcpy(buffer.data(), &obj, sizeof(obj));  // this copies the pointer, not the object!

// ...

auto ptr = *reinterpret_cast<A**>(buffer.data()); // is this UB?
delete ptr;

在这种情况下,reinterpret_cast 的用法是 UB 吗?我会说是的,因为 memcpy 不会启动实例的生命周期,因此违反了严格的别名规则(这就是为什么 std::bit_cast 已添加到 C++20)。

如果我用另一个memcpy(读取指针)替换演员表,程序是否定义良好?

【问题讨论】:

  • 除了语言律师,这完全是错误的。应该是buffer.data() 指向的内容包含指向A 的指针,而不是buffer.data() 本身是指向A 的指针。
  • std::vector 分配的内存是否有任何对齐保证? (我认为它的保证是它的分配器保证。)
  • 我认为它也打破了严格的别名。
  • @anastaciu 在谷歌上的第一击——stats.meta.stackexchange.com/q/5783/3512
  • OK 重新阅读问题后,确实是 UB,因为(1)对齐要求可能被破坏,(2)缓冲区中没有 A* 对象。该标准对标准分配器::分配函数进行了说明:“返回:指向大小为 n * sizeof(T) 的存储数组的初始元素的指针,对于 T 类型的对象适当对齐”。跨度>

标签: c++ language-lawyer


【解决方案1】:

是的,此代码具有未定义的行为。在buffer.data() 指向的位置没有A* 类型的对象。您所做的只是将此类指针的对象表示复制到您的向量[basic.types]/4 中。由于指针是可简单复制的[basic.types]/9,如果您要将这些字节复制回A* 类型的实际对象,然后再复制delete 的值,那将是明确定义的[basic.types]/3。所以这个

A* ptr;
std::memcpy(&ptr, buffer.data(), sizeof(ptr));
delete ptr;

会好的。

请注意,在原始示例中调用未定义行为的不是转换本身,而是您随后尝试读取类型为 A* 的对象的值,该对象在通过转换获得的指针指向的位置不存在。指针指向的所有对象都是unsigned char 类型的对象序列。 A* 类型不是可用于访问 unsigned char [basic.lval]/8 类型对象的存储值的类型...

【讨论】:

  • 我相信这个答案是正确的,但最后一句话有点令人惊讶,因为有大量现有代码基本上按照问题的要求进行(首先是每个自定义容器实现) ,目前在某些情况下并没有真正的好方法。
  • A* a_ptr; std::memcpy(&amp;a_ptr, buffer.data(), sizeof(a_ptr)); 将指针从缓冲区中提取出来。而不是蒂莫问题中的reinterpret_cast
  • 语言律师不谈,为什么这实际上是UB?想到了严格的别名,还有什么别的吗?
  • @MichaelKenzel 那里有一个unsigned char 类型的对象,可能不止一个。
  • @MichaelKenzel 当然它是站不住脚的,因为它是 UB。但是在(当前)C++ 中启动对象生命周期确实很困难,并且大量(否则是高质量的!)代码无法正确执行,特别是参见p0593r2 §2.3,还有 §2.2。既然你提到了压缩:这实际上是我的公司所做的,我们只是说我们的代码库至少不关注这些问题(诚然,因为它大部分来自惯用的 C)。
猜你喜欢
  • 1970-01-01
  • 2016-01-17
  • 2013-09-09
  • 2022-07-29
  • 2014-10-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多