除了调用全局删除运算符外,删除空指针怎么办? [英] How can deleting a void pointer do anything other than invoke the global delete operator?

查看:94
本文介绍了除了调用全局删除运算符外,删除空指针怎么办?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

C ++标准非常清楚明确地指出,在<$ c $上使用 delete delete [] c> void -指针是未定义的行为,如此答案所引用:


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


但是,据我了解, delete delete [] 仅做两件事:




  • 调用适当的析构函数

  • 调用适当的 operator delete 函数,通常是全局函数


只有一个参数 operator delete (以及 operator delete [] )和参数是 void * ptr



因此,当编译器遇到带有 void * 操作数,它当然可以 恶意会完全完成某些操作不相关的操作,或仅不为该表达式输出任何代码。更好的是,尽管我测试过的MSVS,Clang和GCC版本没有这样做,但它可能会发出诊断消息并拒绝编译。 (后两个使用 -Wall 发出警告;使用 / W3 的MSVS不会。)



但是在删除操作中,实际上只有一种明智的方法来处理上述每个步骤:




  • void * 不指定析构函数,因此不调用析构函数。

  • void 不是类型,因此不能具有特定的对应操作符删除,因此全局操作符删除(或 [] 版本)必须被调用。由于函数的参数为​​ void * ,因此不需要类型转换,并且运算符必须正确运行。



因此,可以依靠普通的编译器实现(大概不是恶意的,否则我们甚至都不相信它们遵守标准)来执行上述步骤(释放内存中没有调用析构函数)遇到这种删除表达式时?如果没有,为什么不呢?如果是这样,当数据的实际类型没有析构函数时,以这种方式使用 delete 是否安全(例如,它是基元数组,例如 long [64] )?



全局删除运算符 void运算符delete(void * ptr) void * 数据安全地调用c $ c>(以及相应的数组版本)(再次假设不应调用析构函数)?

解决方案

void * 是指向未知类型对象的指针。如果您不知道某物的类型,就不可能知道该如何破坏某物。因此,我认为,不,没有真的只有一种明智的方式来处理这种删除操作。处理此类删除操作的唯一明智方法是不处理它。因为根本无法正确处理它。



因此,正如您链接的原始答案所述:删除 void * 是未定义的行为( [expr.delete]§2)。该答案中提到的脚注到今天基本上保持不变。老实说,我只是将其指定为未定义的行为而不是使其变得不正确,这使我有点惊讶,因为我无法想到在编译时无法检测到任何情况。



请注意,从C ++ 14开始, new 表达式不一定意味着调用分配函数。 delete 表达式也不一定意味着要调用释放函数。编译器 可以 调用分配函数来获取使用 new 表达式创建的对象的存储。 在某些情况下,允许编译器省略此类调用并使用以其他方式分配的存储空间。例如,这使编译器有时可以将使用 new 创建的多个对象打包到一个分配中。



安全吗?在 void * 上调用全局释放函数,而不是使用 delete 表达式?仅当使用相应的全局分配功能分配存储时。通常,除非您自己调用分配函数,否则您肯定无法确定。如果您是从 new 表达式中获取指针的,则通常不知道该指针是否甚至是释放函数的有效参数,因为它甚至都不能指向通过调用分配函数获得的存储。请注意,知道 new 表达式必须使用哪个分配函数基本上等同于知道 void * 指向。而且,如果您知道这一点,也可以将 static_cast<> 更改为实际类型,然后删除 ... p>

使用琐碎的析构函数来释放对象的存储空间而不先显式调用析构函数是否安全?根据 [basic.life]§1.4,我会说是。请注意,如果该对象是一个数组,则可能仍然必须首先调用任何数组元素的析构函数。除非它们也无关紧要。



您可以依靠常见的编译器实现来产生您认为合理的行为吗?不会。对您 可以确切依赖的内容进行正式定义,实际上就是从头开始制定标准。假设您具有符合标准的实现,则可以依靠该标准为您提供的保证。只要您使用特定编译器的特定版本来编译代码,您还可以依靠特定编译器文档可能提供的任何其他保证。除此之外,所有赌注都关闭了……


The C++ standard very clearly and explicitly states that using delete or delete[] on a void-pointer is undefined behavior, as quoted in this answer:

This implies that an object cannot be deleted using a pointer of type void* because there are no objects of type void.

However, as I understand it, delete and delete[] do just two things:

  • Call the appropriate destructor(s)
  • Invoke the appropriate operator delete function, typically the global one

There is a single-argument operator delete (as well as operator delete[]), and that single argument is void* ptr.

So, when the compiler encounters a delete-expression with a void* operand, it of course could maliciously do some completely unrelated operation, or simply output no code for that expression. Better yet, it could emit a diagnostic message and refuse to compile, though the versions of MSVS, Clang, and GCC I've tested don't do this. (The latter two emit a warning with -Wall; MSVS with /W3 does not.)

But there's really only one sensible way to deal with each of the above steps in the delete operation:

  • void* specifies no destructor, so no destructors are invoked.
  • void is not a type and therefore cannot have a specific corresponding operator delete, so the global operator delete (or the [] version) must be invoked. Since the argument to the function is void*, no type conversion is necessary, and the operator function must behavior correctly.

So, can common compiler implementations (which, presumably, are not malicious, or else we could not even trust them to adhere to the standard anyway) be relied on to follow the above steps (freeing memory without invoking destructors) when encountering such delete expressions? If not, why not? If so, is it safe to use delete this way when the actual type of the data has no destructors (e.g. it's an array of primitives, like long[64])?

Can the global delete operator, void operator delete(void* ptr) (and the corresponding array version), be safely invoked directly for void* data (assuming, again, that no destructors ought to be called)?

解决方案

A void* is a pointer to an object of unknown type. If you do not know the type of something, you cannot possibly know how that something is to be destroyed. So I would argue that, no, there is not "really only one sensible way to deal with such a delete operation". The only sensible way to deal with such a delete operation, is to not deal with it. Because there is simply no way you could possibly deal with it correctly.

Therefore, as the original answer you linked to said: deleting a void* is undefined behavior ([expr.delete] §2). The footnote mentioned in that answer remains essentially unchanged to this day. I'm honestly a bit astonished that this is simply specified as undefined behavior rather than making it ill-formed, since I cannot think of any situation in which this could not be detected at compile time.

Note that, starting with C++14, a new expression does not necessarily imply a call to an allocation function. And neither does a delete expression necessarily imply a call to a deallocation function. The compiler may call an allocation function to obtain storage for an object created with a new expression. In some cases, the compiler is allowed to omit such a call and use storage allocated in other ways. This, e.g., enables the compiler to sometimes pack multiple objects created with new into one allocation.

Is it safe to call the global deallocation function on a void* instead of using a delete expression? Only if the storage was allocated with the corresponding global allocation function. In general, you can't know that for sure unless you called the allocation function yourself. If you got your pointer from a new expression, you generally don't know if that pointer would even be a valid argument to a deallocation function, since it may not even point to storage obtained from calling an allocation function. Note that knowing which allocation function must've been used by a new expression is basically equivalent to knowing the dynamic type of whatever your void* points to. And if you knew that, you could also just static_cast<> to the actual type and delete it…

Is it safe to deallocate the storage of an object with trivial destructor without explicitly calling the destructor first? Based on, [basic.life] §1.4, I would say yes. Note that, if that object is an array, you might still have to call the destructors of any array elements first. Unless they are also trivial.

Can you rely on common compiler implementations to produce the behavior you deem reasonable? No. Having a formal definition of what exactly you can rely on is literally the whole point of having a standard in the first place. Assuming you have a standard-conforming implementation, you can rely on the guarantees the standard gives you. You can also rely on any additional guarantees the documentation of a particular compiler may give you, so long as you use that particular version of that particular compiler to compile your code. Beyond that, all bets are off…

这篇关于除了调用全局删除运算符外,删除空指针怎么办?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆