21

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

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

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

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

4

7 回答 7

19

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

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应是指针值,该指针值是由先前的数组 new-expression产生的”。

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

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

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

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

于 2010-01-26T15:41:12.860 回答
10

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

于 2010-01-26T15:25:18.357 回答
5

至少正如我所读的那样,您有一个静态类型(指针的类型),它不同于动态类型(它指向的对象的真实类型)。在这种情况下,§5.3.5/3 的第二句适用:

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

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

于 2010-01-26T15:42:23.363 回答
5

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[] 是否调用未定义的行为。避开。

于 2010-01-26T16:45:28.430 回答
1

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

然而,C++ 03 标准说结果reinterpret_cast<T1*>(T2*)是未定义的(第 5.2.10.7 节):

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

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

于 2010-01-27T15:23:32.677 回答
0

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

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

示例 1:

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

示例 2:

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

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

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

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

因此在免费之后的东西看起来像这样:

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

于 2010-01-26T15:30:18.110 回答
0

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

于 2010-01-26T17:20:24.963 回答