【问题标题】:Can I new[], then cast the pointer, then delete[] safely with built-in types in C++?我可以使用 C++ 中的内置类型安全地新建 [],然后转换指针,然后删除 [] 吗?
【发布时间】:2011-01-09 13:33:55
【问题描述】:

在我的代码中,我实际上有以下内容:

wchar_t* buffer = new wchar_t[size];
// bonus irrelevant code here
delete[] reinterpret_cast<char*>( buffer );

所讨论的类型都是内置的,因此它们具有微不足道的析构函数。在 VC++ 中,上面的代码可以正常工作 - new[] 只是分配内存,然后 delete[] 只是释放它。

在 C++ 中可以接受吗?是未定义的行为吗?

【问题讨论】:

  • 出于好奇;为什么?您根本不必在 delete 调用之前进行投射。
  • 我对标准的解读是“是的,没关系”,但我真的不确定。对于其他回答者 - 坦率地说,如果没有引用标准,你的意见就不值得。
  • @acron:“不相关的代码”不是一种说法,所有权的传递有好几层,中间有强制转换。我引用的只是“有效代码”。
  • 问题不只是析构函数。这就是内存管理例程 (MMR) 的实现方式。我可以看到实现中的几个选择可能会导致查找动态分配内存的 EndBlock(实现细节)的问题。任何弄乱 MMR 内部结构的东西都可能不会立即被检测到,但绝对是未定义的行为。
  • 我想“为什么?”是正确的问题。它是否符合标准甚至“适用于大多数编译器和运行时”似乎无关紧要。几乎可以肯定,有更好的方法来做你想做的事。

标签: c++ visual-c++ memory-management undefined-behavior


【解决方案1】:

我最初的想法是这是未定义的行为。

5.3.5/3:“在第二种选择(删除数组)中,如果动态 要删除的对象的类型 不同于它的静态类型, 行为未定义。73)

脚注 73 写道:“这意味着不能使用 void* 类型的指针删除对象,因为没有 void 类型的对象”。

可以说,您示例中的对象没有动态类型,因为 1.3.3 中“动态类型”的定义提到了“最衍生的对象”,以及“最衍生的对象”的定义1.8/4 的对象”正在谈论类类型的对象。所以我一直在寻找:

5.2.10/3:“[reinterpret_cast] 可能会或可能不会产生表示 与原始值不同”

5.3.5/2: "delete的操作数的值应该是指针值 这是由先前的数组产生的 新表达式”。

我不确定 reinterpret_cast 是否会产生与输入相同的指针值。可能它已被我尚未找到的其他一些标准所清除。我不会在没有找到明确声明的情况下将此代码称为“OK”,即如果您重新解释一个指针,则结果与之前的“指针值”相同,因此通过将其传递给 delete[],您将传递“指针值"来自新的[]。

5.2.10/7:“除了 [在某些指针类型之间] 和 返回其原始类型会产生 原始指针值,结果 这样的指针转换是 未指定”。

这对我来说似乎是个坏消息——它显然并没有说强制转换产生相同的值,只是说这对强制转换产生了相同的值。这向我表明,允许单个演员表产生不同的值,但这只是暗示性的,而不是明确的。这是“如果标准未说明行为,则行为未定义”的规则的常见问题。仅仅因为它没有在我可以使用索引找到的任何段落中说明它,并不意味着它没有在其他地方说明它......

我们知道,在实践中,我们可以将事物转换为 unsigned char* 以检查其字节,或 void* 以使用 memcpy 复制 POD,因此必须保证某些转换可以创建别名。您可能会认为,如果您的实现确实创建了具有某些强制转换的别名,那么您将传入从 new[] 获得的“相同值”。但我仍然不确定删除 [] 是否足够好。我想我错过了一些重要的东西。

【讨论】:

  • 你比我早了大约 25 秒!然而,在我的辩护中,我让你打败了,直到网络决定打嗝并放弃我第一次发布答案的尝试。 :-)
  • 这不是指派生类型吗? char * 并没有真正的“动态类型”。
  • @Jerry:我只在同样的问题结束后才发帖,纯属侥幸。 @Neil:是的,我只是在想同样的想法并为此进行编辑。我还不确定这是否意味着行为已被定义:如果你 reinterpret_cast 一个指针,结果指针是否“具有相同的值”?
  • @Steve 是时候点亮 LITB 信号了!
  • 我很欣赏这个回复中的细节,但我认为正确的答案是,如果需要一名标准律师来证明某些行为确实被定义,我无论如何都想避免它。
【解决方案2】:

这是未定义的行为,因为delete[] 调用了错误的析构函数。但是,wchar_tchar 是 POD,因此它们没有专用的析构函数,delete[] 所做的只是调用堆实现以释放指针。因此,它最有可能工作,没有字节丢失。但严格来说它仍然是未定义的。

【讨论】:

  • POD 是“可简单破坏的”,这意味着它们实际上没有 析构函数;那么delete[] 怎么可能调用“错误的析构函数”呢?
【解决方案3】:

至少正如我所读到的,您有一个静态类型(指针的类型),它不同于动态类型(它指向的对象的真实类型)。既然如此,则适用第 5.3.5/3 条第二句:

在第二种选择(删除数组)中,如果 要删除的对象与其静态类型不同,行为未定义。

编辑:由于您显然想要分配“原始”内存缓冲区而不是对象数组,因此我建议使用::operator new 而不是new[]。在这种情况下,您正在做的事情是明确定义的,并且还给读者一个明确的意图指示。

【讨论】:

    【解决方案4】:

    iso14882 第 5.2.10.3 节:

    The mapping performed by reinterpret_cast is is implementation defined

    iso14882 第 5.3.5.2 节:

    The value of the operand of delete[] shall be the pointer value which resulted from a previous array new-expression

    换句话说,它的实现定义了 delete[] 是否调用未定义的行为。避开。

    【讨论】:

      【解决方案5】:

      由于wchar_tchar 都是内置类型,因此将调用正确的释放函数(void operator delete(void* ptr)),并且没有可调用的析构函数。

      但是 C++ 03 标准说 reinterpret_cast&lt;T1*&gt;(T2*) 的结果是未定义的(第 5.2.10.7 节):

      指向对象的指针可以显式转换为指向不同类型对象的指针。除了那个 将“指向 T1 的指针”类型的右值转换为“指向 T2 的指针”类型(其中 T1 和 T2 是对象类型 并且 T2 的对齐要求不比 T1 更严格)并返回其原始类型 产生原始指针值,这种指针转换的结果是未指定的。

      从实际的 POV 来看,我无法想象 wchar_t* 值不是有效的 char* 值的实现,因此您的代码应该在所有平台上都可以。只是不符合标准...

      【讨论】:

        【解决方案6】:

        delete[] 运算符在内部使用某种形式的循环来破坏数组的元素。如果元素是不同的对象,则将使用不同的析构函数——这可能会导致未定义的行为。由于 是 wchar 和 char - 原始类型 - 它可能不会导致任何不良行为。

        警告:如果您继续阅读,后果自负!对未来未定义行为的粗略描述。这仅用于教育目的。

        示例 1:

        如果您有两个大小相同的对象,并且它们的析构函数所做的所有操作都将内存清零,那么它可能不会导致不良行为。

        示例 2:

        但是,如果您有两个对象,其中一种类型封装了一个资源的单个 4 字节句柄,而另一种类型具有两个这样的元素,并且您将后者的数组转换为单数情况 - 那么您将泄漏一半你的数组的句柄。情况如下:

        ..2:[1|2][1|2]免费..

        其中“2:”表示数组的大小。在向下转换之后,编译器将生成一个删除,将数据视为:

        ..2:[1][1]免费...

        因此,免费之后的东西会是这样的:

        ..免费[1|2]免费..

        【讨论】:

        • 我的印象是未定义的行为未定义的行为。这可能包括做想要的事情,但这超出了语言所保证的范围。 (例如 ++i + i++ 是未定义的,无论它是否产生您期望的结果。)
        • 当然,我只是把它放在那里讨论。 OP提到了一个极端案例......我刚刚提出了一个扩展这个极端案例范围的讨论。它可能对某人有用。我已将“未定义”更改为“不受欢迎”
        • 是的,但是“可能不会导致未定义的行为”是没有意义的。诸如“可能不会做任何坏事”之类的东西是合理的,但是如果确实存在未定义行为的可能性,那么该行为就是未定义的。看到很多对“未定义行为”的误解后,我倾向于对此非常挑剔。
        • 好了 :D 现在没有混淆的余地了。
        【解决方案7】:

        为什么要使用 reinterpret_cast<..>?如果你用纯 C++ 编写东西,那么你不需要重新解释演员表。在您的情况下,您没有为对象分配内存。您正在为 wchar_t 分配内存。为什么不使用字符串而不是 wchar_t 的数组?

        【讨论】:

          猜你喜欢
          • 2011-04-03
          • 2018-02-19
          • 1970-01-01
          • 2018-09-14
          • 2018-06-29
          • 2015-09-17
          • 1970-01-01
          • 1970-01-01
          • 2016-05-22
          相关资源
          最近更新 更多