问题 将reinterpret_cast输入重新解释为std :: unique_ptr是不是真的安全?


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

例:

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

这允许`v为结构本身提供视图,这是优选的,因为我们不需要第二个变量,除了删除之外我们不关心字节指针。

问题在于可能会在演员表上敲击指针(使其免于安全)。我认为没有合理的理由为什么编译器会改变转换时的指针值(因为没有继承),但是我听说标准保留了对任何转换的任何指针的权利,所以就标准兼容的编码而言,办法是窗外的吧?或者有什么理由它是安全的吗?有没有办法至少static_assert这个,或其他一些方法来使它安全或干净地处理这种类型的结构?


12840
2018-03-04 06:22


起源

供参考,是否 reinterpret_cast 实际上改变指针值: stackoverflow.com/questions/3298774/... - John Zwinck
如果你没有绕过指针,只是希望它被自动删除,也许可以考虑一个非常小的包装类,即使在函数内只使用一次。 - Segmented


答案:


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

  • 分配的内存没有VARIABLE_SIZE_STRUCT放置的对象 - newed in it - 假设你照顾它, 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*>


5
2018-03-04 06:41



谢谢,我完全在我的例子中传递了delete []的必要性。值得庆幸的是,这是对我们的代码的人为简化描述,所以我真的没有这个错误,但仍然,这是一个很好的捕获。 - VoidStar
此外,VARIABLE_SIZE_STRUCT和几乎所有类似的都是C结构,因此不需要放置new。虽然我猜有人可能会尝试使用C ++中的模式,但如果它是C ++类,它们可以提供自己的创建函数。 - VoidStar
@VoidStar你可以使用std :: is_pod作为static_assert来排除这种情况,否则我同意Tony D,新的安置会更安全。 - Segmented
@VoidStar: “......但他们可以提供自己的创作功能”  - 我们无法确定你是不是“他们”试图做到这一点;-)。无论如何,人们有时候仍然会在C ++中做这样的事情,想要避免为变量组件额外/单独的内存分配,或者因为它们在二进制数据级别进行接口,但这并不排除想要使用构造函数来初始化领域...惊人的你可以在那里找到 - 偶尔合理,但往往只是悲伤。 - Tony Delroy


你是对的,这是不安全的。但是,它可以使其安全。

标准保证,如果你 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


5
2018-03-04 06:39



“该标准保证,如果您将reprepret_cast重新设置为其他类型,然后返回原始类型,则会返回原始值。”  - 根据5.2.10 / 7,只有在保证的情况下才能保证 VARIABLE_SIZE_STRUCT 对齐要求不比更严格 BYTE,这很可能是不真实的。它无论如何都适用于大多数架构,但标准不能保证。 - Tony Delroy
你可以通过乱搞std :: aligned_storage来使它安全。 - Segmented
“在这一点上,你可能最好创建自己的包装器而不是使用unique_ptr”,我非常不同意,使用一个标准类,每个人都知道,经过良好测试的实现比其他人必须理解的自定义类要好得多这也可以很容易地引入新的bug。自定义删除器是一个很棒的功能,存在的原因很充分:) - Drax