【问题标题】:Is the pointer guaranteed to preserve its value after `delete` in C++?在 C++ 中的“删除”之后,指针是否保证保留其值?
【发布时间】:2011-06-27 11:41:28
【问题描述】:

灵感来自this question。

假设在 C++ 代码中我有一个有效的指针并且正确地delete 它。根据 C++ 标准,指针将变为无效(3.7.3.2/4 - 释放函数将使引用所有已释放存储部分的指针无效)。

至少在大多数实现中,它会保留该值,并将存储与之前delete 完全相同的地址,但是using the value is undefined behavior。

标准是否保证指针将保留其值,或者该值是否允许更改?

【问题讨论】:

  • delete 签名是否允许它访问指针,即除按值传递之外的任何内容?
  • 有趣的问题,但纯粹是学术上的好奇心,我希望。我无法想象为什么您在编写代码时需要知道这一点。
  • @Cody Gray:你是对的。在delete是UB之后使用指针(例如,试图printf()它),因此用户甚至无法合法地读取指针并与原始值进行比较。
  • @Rup:在这种情况下,Delete 是一个没有签名的运算符。 (“删除操作符”是删除操作符调用或显式调用的函数。)
  • @FredNurk:更清楚地说,它是一个调用运算符 (operator delete) 来委派其工作的关键字。并且该关键字没有“签名”。

标签: c++ pointers delete-operator


【解决方案1】:

不,不能保证,实现可以合法地将零分配给delete的左值操作数。

Bjarne Stroustrup 曾希望实现选择这样做,但没有多少人这样做。

http://www.stroustrup.com/bs_faq2.html#delete-zero

【讨论】:

  • 它可以分配除零以外的任何其他地址吗?
  • 这几乎没用,因为指针的所有副本都不会改变......
  • @MatthieuM.:我并不是说我同意 Bjarne 在这一点上。 stackoverflow.com/questions/1265666/…
  • @sharptooth:是的,它可以(几乎)做任何事情。
【解决方案2】:

如果出于某种原因,您想确保指针变量不会被delete更改,请写:

delete p + 0;

【讨论】:

  • 有趣的方式,我会说删除指针的副本,但也可以。
【解决方案3】:

我相信大多数实现都会保留该值,只是为了没有理由更改它。但是不管值是否保留,它仍然是一个无用的指针,不是吗?

【讨论】:

  • 是的,不管怎样都没用。但是,如果您尝试使用它,您遇到的错误的性质将完全不同。
【解决方案4】:

全局操作符delete的签名,按照标准3.7.3.2/2的要求:

每个释放函数都应返回 void,其第一个参数应为 void*。

这意味着delete不能修改你传递给它的指针,它会一直保留它的值。

【讨论】:

  • 函数“operator delete”不是删除操作符。该函数的签名与操作员是否可以修改值无关。 (当然,该函数本身不能修改任何值。它甚至不能通过引用获取指针或指向指针的指针,因为它没有来自原始指针的类型信息。)
【解决方案5】:

考虑一下,您将如何检查或依赖任何“是”或“否”的答案?你不能。或者,您可以,但检查的结果(空指针除外)是未定义行为。

delete 后面不能检查非空值,所以问题一般没有意义。

另外,delete 的参数可以是右值表达式,所以这个问题没有意义。

干杯,

【讨论】:

  • 当然,您可以通过查看内存中字节的值来检查,记住它们,然后再查看。确实,这些字节之前可能是指针值表示,之后(比如说)是陷阱表示,所以从某种意义上说,没有“后值”我们可以问,“它改变了吗?”。但是询问例如的输出是有意义的。 char *x = 0; char one = *(char*)(&x); delete x; char two = *(char*)(&x); std::cout << (one == two);。如果它输出false,我认为可以说x 发生了一些变化,如果不是精确的指针值。
  • (实际上,我认为我需要char *x = new char;,因为空指针是一种特殊情况)。此外,delete can 的参数是右值这一事实与它是左值时发生的情况没有直接关系。当然,这可能会向实现者暗示,在这两种情况下做不同的事情是不必要的。
  • @Steve:序列化指针值(例如将它们存储在文件中)通常不好,但可以想象在 POD 结构中具有指针值并将其与早期副本按位进行比较。我认为这是非常边缘的情况。所以,一般来说,没有意义。
  • “一般”的意思是,“除非它是有意义的,因为它会影响严格符合程序的行为”?诚然,这不是您或我有任何动机编写的严格遵守的程序。老实说,我认为您希望您的回答会有所帮助的希望很渺茫;-)
  • @PravasiMeet:最简单的现实例子可能是delete foo(),其中foo 是一个提供指针值的函数。语言创建者 Bjarne Stroustrup 在his FAQ item about this 中也提供了一些示例。
【解决方案6】:

不保证指针本身具有任何有意义的值,除了它被分配的范围和超出该范围的末尾。

您可能会质疑的是,例如,您是否在进行自己的泄漏检查,因此您编写了函数来在您完成删除后从映射中删除指针。那将使用 std::less ,它保证可以与不在一个范围内的指针一起工作,并且可能也可以与指向不再有效的内存的指针一起工作。

当然,您可能会让垃圾收集器在删除它指向的内存之前进行“删除”。

与标准一样,如果您传递给 delete 的值不是左值,则保证保持相同的值,但如果是左值,则由实现定义。

【讨论】:

    【解决方案7】:

    这个问题很重要!

    我看到 Visual Studio 2017 在 delete 之后更改了指针的值。它引起了一个问题,因为我使用了一个内存跟踪工具,该工具在每个运算符 new 之后收集指针,并在 delete 之后检查它们。伪代码:

    Data* New(const size_t count)
    {
        Data* const ptr(new Data[count]);
        #ifdef TEST_MODE
        DebugMemory.Collect(ptr);
        #endif
        return ptr;
    }
    
    void Delete(Data* const ptr)
    {
        delete[] ptr;
        #ifdef TEST_MODE
        DebugMemory.Test(ptr);
        #endif
    }
    

    此代码在 Visual Studio 2008 中运行良好,但在 Visual Studio 2017 中失败,因此我更改了第二个函数中的操作顺序。

    但是,问题是好的,问题是存在的。经验丰富的工程师应该意识到这一点。

    【讨论】:

      猜你喜欢
      • 2011-10-26
      • 1970-01-01
      • 1970-01-01
      • 2012-09-27
      • 1970-01-01
      • 2014-09-27
      • 1970-01-01
      • 2014-01-10
      • 2021-03-21
      相关资源
      最近更新 更多