在C++中,指针在使用`delete`后是否保证保留其值?

20
受到这个问题的启发。
假设在C++代码中我有一个有效的指针并正确地进行delete。根据C++标准,该指针将变为无效 (3.7.3.2/4 - 分配函数将使所有部分存储器都失效)。
至少在大多数实现中,它会保留值并且会存储与delete之前完全相同的地址,但是使用该值是未定义的行为
标准是否保证指针将保留其值,还是允许该值更改?

4
delete 签名允许它访问指针,也就是说除了传递值之外还允许其他操作吗? - Rup
2
有趣的问题,但我希望这只是纯学术上的好奇心。在编写代码时,我无法想象为什么你需要知道这个。 - Cody Gray
1
@Cody Gray:你说得对。在delete之后使用指针(例如,尝试printf()它)是未定义行为,因此用户甚至不能合法地读取指针并将其与原始值进行比较。 - sharptooth
2
@Rup:在这种情况下,delete是一个没有签名的运算符。(“Operator delete”是由delete运算符或显式调用的函数。) - Fred Nurk
2
@FredNurk:更清晰的说法可能是它是一个关键字,调用一个操作符(operator delete)来委托它的工作。而且这个关键字没有“签名”。 - Lightness Races in Orbit
显示剩余13条评论
7个回答

22

1
它能分配除零以外的任何其他地址吗? - sharptooth
1
这段程序相关的内容翻译成中文:这几乎是无用的,因为指针的所有副本都不会改变... - Matthieu M.
1
@MatthieuM:我并不认为在这一点上我同意Bjarne的观点。https://dev59.com/PnM_5IYBdhLWcg3wslfs#1265866 - CB Bailey
@sharptooth:是的,它可以做(几乎)任何事情。 - CB Bailey

15

如果出于任何原因,你想确保指针变量没有被delete更改,那么请写:

delete p + 0;

有趣的方式,我本来会说删除指针的副本,但是没错,这也可以。 - CashCow

3

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


是的,无论如何都是无用的。但是,如果您尝试使用它,遇到的错误性质将完全不同。 - Mark Ransom

1

这个问题非常重要!

我发现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中失败了,所以我已经改变了第二个函数的操作顺序。
然而,这个问题是存在的,有经验的工程师应该意识到这一点。

1
指针本身并不保证具有任何有意义的值,除了它被分配的范围和这一范围的结束位置之外。
你可能要质疑的是,比如说,如果你正在进行自己的泄漏检查,那么你编写了一个函数,在删除后从 map 中删除指针。它将使用 std::less,该函数保证适用于不指向范围内的指针,并且也可能适用于指向不再有效的内存的指针。
当然,在删除其所指向的内存之前,您可以让垃圾回收来执行“删除”操作。
标准规定,如果您传递给 delete 的值不是左值,则保证保持相同的值,但如果它是左值,则是实现定义的。

1

考虑一下,你如何检查或依赖于任何“是”或“否”的答案?你不能。或者说,你可以,但是那个检查的结果(除了nullpointer)是未定义行为。

delete之后,你无法检查非空值,因此这个问题通常是没有意义的。

此外,delete的参数可以是一个rvalue表达式,所以这个问题是没有意义的。

祝好!


1
你可以通过查看内存中字节的值,记住它们,然后再次查看来进行检查。确实,这些字节在之前可能是指针值表示,在之后可能是陷阱表示,因此从某种意义上说,我们无法询问“是否已更改”关于任何“后值”。但是,询问例如char *x = 0; char one = *(char*)(&x); delete x; char two = *(char*)(&x); std::cout << (one == two);的输出是有意义的。如果输出为false,我认为可以说x的某些内容已经发生了变化,尽管不是指针值。 - Steve Jessop
(实际上,我认为我需要 char *x = new char;,因为空指针是一种特殊情况)。另外,delete 的参数可以是 rvalue 并不直接影响它作为 lvalue 时会发生什么。当然,这可能会建议实现者在这两种情况下做不同的事情是不必要的努力。 - Steve Jessop
@Steve:通常不建议序列化指针值(例如将它们存储在文件中),但是在POD结构中可能会有一个指针值,并且可以按位比较以前的副本。我认为这是一个非常边缘的情况,因此一般来说没有意义。 - Cheers and hth. - Alf
“一般情况下”是指,“除非它在影响严格符合规范的程序行为方面具有意义”。当然,我们没有任何动机编写不符合规范的程序。老实说,我认为你希望这个答案能够帮到你的希望是徒劳的;-) - Steve Jessop
1
@PravasiMeet:最简单的现实例子可能是delete foo(),其中foo是提供指针值的函数。语言创始人Bjarne Stroustrup在他关于此问题的FAQ条目中也提供了一些例子。 - Cheers and hth. - Alf
显示剩余2条评论

0

全局 operator delete 的签名,符合标准 3.7.3.2/2 的要求:

每个释放函数都应该返回 void,并且它的第一个参数应该是 void*。

这意味着 delete 不能修改您传递给它的指针,并且它将始终保留其值。


7
“operator delete”函数并不是删除运算符。该函数的签名与运算符是否能够修改值无关。(当然,该函数本身不能修改任何值。它甚至不能通过引用或指向指针的指针来获取指针,因为它没有原始指针的类型信息。) - Fred Nurk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接