98

假设我有以下代码:

void* my_alloc (size_t size)
{
   return new char [size];
}

void my_free (void* ptr)
{
   delete [] ptr;
}

这安全吗?还是必须在删除之前强制转换ptrchar*

4

13 回答 13

149

C++ 标准未定义通过 void 指针删除 - 请参阅第 5.3.5/3 节:

在第一种选择(删除对象)中,如果操作数的静态类型与其动态类型不同,则静态类型应为操作数动态类型的基类,并且静态类型应具有虚拟析构函数或行为未定义. 在第二种选择(删除数组)中,如果要删除的对象的动态类型与其静态类型不同,则行为未定义。

及其脚注:

这意味着不能使用 void* 类型的指针删除对象,因为没有 void 类型的对象

.

于 2009-06-02T21:15:21.350 回答
26

这不是一个好主意,也不是你会在 C++ 中做的事情。您无缘无故地丢失了类型信息。

当您为非原始类型调用析构函数时,不会在要删除的数组中的对象上调用析构函数。

您应该改写新/删除。

删除 void* 可能会偶然正确释放您的内存,但这是错误的,因为结果未定义。

如果出于某种我不知道的原因,您需要将指针存储在 void* 中然后释放它,您应该使用 malloc 和 free。

于 2009-06-02T20:52:22.927 回答
25

这取决于“安全”。它通常会起作用,因为信息与分配本身的指针一起存储,因此释放器可以将其返回到正确的位置。从这个意义上说,只要您的分配器使用内部边界标签,它就是“安全的”。(很多人都这样做。)

但是,正如其他答案中提到的,删除 void 指针不会调用析构函数,这可能是一个问题。从这个意义上说,它并不“安全”。

没有充分的理由按照你正在做的事情做你正在做的事情。如果您想编写自己的释放函数,可以使用函数模板生成具有正确类型的函数。这样做的一个很好的理由是生成池分配器,这对于特定类型可能非常有效。

正如其他答案中提到的,这是C++ 中未定义的行为。一般来说,最好避免未定义的行为,尽管主题本身很复杂并且充满了相互矛盾的意见。

于 2009-06-02T21:14:17.780 回答
13

删除 void 指针是危险的,因为不会在它实际指向的值上调用析构函数。这可能会导致应用程序中的内存/资源泄漏。

于 2009-06-02T21:05:14.913 回答
9

这个问题没有意义。您的困惑可能部分是由于人们经常使用的草率语言delete

您用于delete销毁动态分配的对象。这样做,您将形成一个带有指向该对象的指针的删除表达式。您永远不会“删除指针”。您真正要做的是“删除由其地址标识的对象”。

现在我们明白为什么这个问题没有意义了:空指针不是“对象的地址”。它只是一个地址,没有任何语义。它可能来自一个实际对象的地址,但该信息丢失了,因为它是以原始指针的类型编码的。恢复对象指针的唯一方法是将 void 指针转换回对象指针(这需要作者知道指针的含义)。void本身是不完整的类型,因此永远不是对象的类型,并且永远不能使用 void 指针来标识对象。(对象由它们的类型和地址共同标识。)

于 2013-09-08T12:35:14.440 回答
8

如果你真的必须这样做,为什么不去掉中间人(newdelete运算符)并直接调用全局operator newoperator delete?(当然,如果您尝试使用newanddelete运算符,您实际上应该重新实现operator newand operator delete。)

void* my_alloc (size_t size)
{
   return ::operator new(size);
}

void my_free (void* ptr)
{
   ::operator delete(ptr);
}

请注意,不像malloc(),operator new会引发std::bad_alloc失败(或调用new_handlerif 已注册)。

于 2009-06-04T07:34:10.523 回答
6

很多人已经评论说不,删除空指针是不安全的。我同意这一点,但我还想补充一点,如果您正在使用 void 指针来分配连续数组或类似的东西,您可以这样做,new以便您可以delete安全地使用(用,啊哼,一点额外的工作)。这是通过为内存区域(称为“arena”)分配一个 void 指针,然后将指向该 arena 的指针提供给 new 来完成的。请参阅C++ FAQ中的此部分。这是在 C++ 中实现内存池的常用方法。

于 2009-06-02T21:14:11.433 回答
5

因为 char 没有特殊的析构逻辑。这行不通。

class foo
{
   ~foo() { printf("huzza"); }
}

main()
{
   foo * myFoo = new foo();
   delete ((void*)foo);
}

d'ctor 不会被叫到。

于 2009-06-02T21:07:57.873 回答
5

如果你想使用 void*,为什么不只使用 malloc/free?new/delete 不仅仅是内存管理。基本上, new/delete 调用构造函数/析构函数,还有更多的事情发生。如果您只是使用内置类型(如 char*)并通过 void* 删除它们,它会起作用,但仍然不推荐。如果您想使用 void*,底线是使用 malloc/free。否则,为方便起见,您可以使用模板函数。

template<typename T>
T* my_alloc (size_t size)
{
   return new T [size];
}

template<typename T>
void my_free (T* ptr)
{
   delete [] ptr;
}

int main(void)
{
    char* pChar = my_alloc<char>(10);
    my_free(pChar);
}
于 2009-06-02T21:33:07.620 回答
0

如果您只想要一个缓冲区,请使用 malloc/free。如果您必须使用 new/delete,请考虑一个简单的包装类:

template<int size_ > struct size_buffer { 
  char data_[ size_]; 
  operator void*() { return (void*)&data_; }
};

typedef sized_buffer<100> OpaqueBuffer; // logical description of your sized buffer

OpaqueBuffer* ptr = new OpaqueBuffer();

delete ptr;
于 2009-06-03T13:32:56.803 回答
0

几乎没有理由这样做。

首先,如果您不知道数据的类型,而您只知道它是void*,那么您真的应该将该数据视为二进制数据的无类型blob ( unsigned char*),并使用malloc/free来处理它. 有时对于波形数据等需要这样做,您需要将void*指针传递给 C api。没关系。

如果你确实知道数据的类型(即它有一个ctor/dtor),但由于某种原因你最终得到了一个void*指针(无论你有什么原因),那么你真的应该把它转换回你知道的类型be,并呼吁delete它。

于 2013-05-07T01:25:55.193 回答
0

我在我的框架中使用了 void*,(又名未知类型),同时在代码反射和其他歧义的壮举中,到目前为止,我没有遇到任何编译器的问题(内存泄漏、访问冲突等)。仅因操作不规范而发出警告。

删除一个未知数 (void*) 非常有意义。只需确保指针遵循这些准则,否则它可能不再有意义:

1) 未知指针不能指向具有平凡解构函数的类型,因此当转换为未知指针时,它永远不应该被删除。仅在将未知指针转换回 ORIGINAL 类型后删除它。

2) 实例是否在堆栈绑定或堆绑定内存中被引用为未知指针?如果未知指针引用堆栈上的一个实例,那么它永远不应该被删除!

3)你100%肯定未知指针是一个有效的内存区域吗?不,那么它永远不应该被删除!

总之,使用未知 (void*) 指针类型可以完成的直接工作非常少。然而,间接地,void* 是 C++ 开发人员在需要数据模糊性时依赖的重要资产。

于 2014-01-04T23:17:05.937 回答
0

对于 char 的特殊情况。

char 是一种没有特殊析构函数的内在类型。因此,泄漏论点是一个没有实际意义的论点。

sizeof(char) 通常是一个,因此也没有对齐参数。在 sizeof(char) 不是 1 的稀有平台的情况下,它们分配的内存足够对齐到它们的 char。所以对齐论点也是一个没有实际意义的论点。

malloc/free 在这种情况下会更快。但是你放弃了 std::bad_alloc 并且必须检查 malloc 的结果。调用全局 new 和 delete 操作符可能会更好,因为它绕过了中间人。

于 2015-02-01T03:47:02.647 回答