reinterpret_cast把输入转换成std::unique_ptr永远不是完全安全的吗?

11

当使用具有可变大小结构(必须分配为byte[],然后转换为struct的结构)的各种API时,如果unique_ptr holder可以指向该结构,那么将会很好,因为这就是我们将要使用的内容。

示例:

std::unique_ptr<VARIABLE_SIZE_STRUCT[]> v; 
v.reset(reinterpret_cast<VARIABLE_SIZE_STRUCT*>(new BYTE[bytesRequired]));

这使得`v能够为结构本身提供视图,这是首选的,因为我们不需要第二个变量,并且除了删除之外,我们不关心字节指针。

问题出在可能在转换时对指针进行thunking(使其无法安全释放)。我看不到编译器在转换时更改指针值的合理原因(因为没有继承),但我听说标准保留了在任何转换中thunk任何指针的权利,因此就符合标准的编码而言,这种方法已经没有用了,对吗?或者说它是否有安全性的理由?有没有办法至少进行静态断言,或者其他方式使其安全或干净地处理这种类型的结构?


供参考,关于reinterpret_cast是否会实际改变指针的值:https://dev59.com/HHA75IYBdhLWcg3wbojM - John Zwinck
如果你不想传递指针,而只是希望它在使用完后自动删除,也许可以考虑一个非常小的包装类,甚至可以在函数内部定义,如果它只被使用一次。 - Segmented
2个回答

5

你说得对,这是不安全的。但是有可能使它变得安全。

标准保证如果你使用 reinterpret_cast 转换到另一种类型,然后再转回原始类型,你会得到原始值。

你可以与自定义删除器一起使用,确保在释放内部指针之前将其转换回分配时的类型。

auto deleter = [](VARIABLE_SIZE_STRUCT* ptr) 
{ 
    delete[] reinterpret_cast<uint8_t*>(ptr); 
}; 

std::unique_ptr<VARIABLE_SIZE_STRUCT, decltype(deleter)> v
    (reinterpret_cast<VARIABLE_SIZE_STRUCT*>(new uint8_t[256]), deleter);

此时,您最好创建自己的包装器而不是使用unique_ptr


2
“标准保证,如果您使用reinterpret_cast将类型重新解释为不同类型,然后再次转换回原始类型,则会得到原始值。” - 在5.2.10/7下,只有在VARIABLE_SIZE_STRUCT的对齐要求不严格于BYTE时才能保证,这很可能是不正确的。它在大多数体系结构上都可以工作,但不被标准保证。 - Tony Delroy
你可能可以通过使用std::aligned_storage来严格控制程序的安全。 - Segmented
1
此时,你最好创建自己的包装器而不是使用unique_ptr。我强烈反对这种观点。使用每个人都知道并具有经过充分测试实现的标准类要比其他人必须理解的自定义类高度优越,后者也很容易引入新的错误。自定义删除器是一个非常棒的功能,存在有其合理的原因 :) - Drax

5
  • 您分配的内存可能没有VARIABLE_SIZE_STRUCT所需的对齐方式。

  • 分配的内存中没有放置VARIABLE_SIZE_STRUCT的对象——假设您能处理它,那么unique_ptr的默认析构逻辑将找到预期的对象实例进行销毁,但是释放内存本身不会使用delete []来删除一个BYTE*——为了获得定义行为,您必须定制删除器以先调用~VARIABLE_SIZE_STRUCT()然后再调用delete[]...

如果您担心"thunking",可以在运行时进行检查:

BYTE* p;
v.reset(reinterpret_cast<VARIABLE_SIZE_STRUCT*>(p = new BYTE[bytesRequired]));
assert(reinterpret_cast<BYTE*>(v.get()) == p);

关于此事的背景 - 5.2.10/7:

对象指针可以显式地转换为不同类型的对象指针。当对象指针类型的 prvalue v 被转换为对象指针类型“指向 cv T”的时候,结果是 static_cast<cvT*>(static_cast<cv void*>(v))。将类型为“指向 T1”的 prvalue 转换为类型“指向 T2”(其中 T1 和 T2 是对象类型,并且 T2 的对齐要求不比 T1 更严格),然后再转回原始类型,就会得到原始指针值。

因此,如果 VARIABLE_SIZE_STRUCT 的对齐要求比 BYTE 更严格,使用 reinterpret_cast<BYTE*> 不能保证能够检索到原始指针。


谢谢,我完全忽略了在我的示例中需要使用delete[]。幸运的是,这只是我们代码的人为简化描述,所以我并没有真正遇到这个错误,但还是很不错的发现。 - VoidStar
另外,VARIABLE_SIZE_STRUCT和几乎所有类似的结构都是C结构,因此不需要使用放置new。虽然我猜有人可能会在C++中尝试这种模式,但如果它是一个C++类,他们可以提供自己的创建函数。 - VoidStar
2
@VoidStar 你可以使用std::is_pod作为静态断言来排除这种情况,否则我同意Tony D的观点,放置新对象会更安全。 - Segmented
1
@VoidStar: “...但他们可以提供自己的创建函数” - 我们无法知道你是否是“他们”,试图做到这一点;-)。 无论如何,人们有时仍然会在C ++中做这样的事情,希望避免变量组件的额外/单独内存分配,或者因为他们正在以二进制数据级别进行接口处理,但这并不排除想要使用构造函数来初始化字段...令人惊讶的是你能在那里找到什么 - 偶尔是有道理的,但更常见的是悲哀的。 - Tony Delroy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接