删除指针后,指针保持的地址会发生变化

时间:2013-12-22 00:40:39

标签: c++ pointers delete-operator

在以下代码中,为什么指针x保留的地址在delete之后发生了变化?据我所知,delete调用应释放堆中已分配的内存,但不应更改指针地址。

using namespace std;
#include <iostream>
#include <cstdlib>

int main()
{
    int* x = new int;
    *x = 2;

    cout << x << endl << *x << endl ;

    delete x;

    cout << x << endl;

    system("Pause");
    return 0;
}

OUTPUT:
01103ED8
2
00008123

观察:我正在使用Visual Studio 2013和Windows 8.据说这在其他编译器中不起作用。另外,我理解这是不好的做法,我应该在删除指针后重新指定NULL,我只是想了解是什么驱使这种奇怪的行为。

2 个答案:

答案 0 :(得分:5)

  

据我所知,deletecall应释放堆中已分配的内存,但不应更改指针地址。

好吧,为什么不呢?这是完全合法的输出 - 在删除指针后读取指针会导致未定义的行为。这包括指针的值改变。 (事实上​​,这甚至不需要UB; delete d指针实际上可以指向任何地方。)

答案 1 :(得分:1)

阅读了C ++ 98和C ++ 11 [N3485]的相关位,以及H​​2CO3指出的所有内容:

这两个版本的标准都没有充分描述&#34;无效指针&#34;是,在什么情况下创建它们,或者它们的语义是什么。因此,我不清楚OP的代码是否意图引发未定义的行为,但事实它确实(因为标准所做的任何事情)没有明确定义是,重复,未定义)。该文本在C ++ 11中得到了改进,但仍然不足。

作为语言设计的问题,以下程序肯定会显示未指定的行为,这很好。 可能,但不应该也表现出未标明的行为;换句话说,到这个程序表现出未定义的行为的程度,即IMNSHO标准中的缺陷。具体地说,复制&#34;无效&#34;的值。指针,并在这些指针上执行相等比较,不应该是UB。我特别拒绝了假设硬件的相反论点,该硬件只是将指向未映射内存的指针加载到寄存器中。 (注意:我在C ++ 11中找不到与C11 6.5.2.3脚注95相对应的文本,关于编写一个联盟成员和阅读另一个联合成员的合法性;该程序假定此操作的结果是未指定但不是 undefined (除非它可能涉及陷阱表示),因为它在C中。)

#include <string.h>
#include <stdio.h>

union ptr {
    int *val;
    unsigned char repr[sizeof(int *)];
};

int main(void)
{
    ptr a, b, c, d, e;

    a.val = new int(0);
    b.val = a.val;
    memcpy(c.repr, a.repr, sizeof(int *));

    delete a.val;
    d.val = a.val; // copy may, but should not, provoke UB
    memcpy(e.repr, a.repr, sizeof(int *));

    // accesses to b.val and d.val may, but should not, provoke UB
    // result of comparison is unspecified (may, but should not, be undefined)
    printf("b %c= d\n", b.val == d.val ? '=' : '!');

    // result of comparison is unspecified
    printf("c %c= e\n", memcmp(c.repr, e.repr, sizeof(int *)) ? '!' : '=');
 }

这是C ++ 98的所有相关文本:

  

[3.7.3.2p4]如果参数赋予标准库中的释放函数     是一个指针,它不是空指针值(4.10),即释放函数     应解除分配指针引用的存储空间,使其全部无效     指向 deallocated storage 的任何部分的指针。使用效果     无效的指针值(包括将其传递给释放函数)是     未定义。 [脚注:在某些实现中,它会导致系统生成     运行时故障。]

问题在于没有定义&#34;使用无效的指针值&#34;,所以我们争论什么是合格的。委员会在讨论迭代器(一个被定义为包括裸指针的类别)的意图中有一条线索:

  

[24.1p5] ...迭代器也可以具有与任何容器无关的奇异值。 [示例:在声明未初始化的指针x之后(与int* x; [sic]一样),x必须始终假定为具有奇异值一个指针。]对于奇异值,大多数表达式的结果都是未定义的。唯一的例外是将非奇异值赋给包含奇异值的迭代器。在这种情况下,奇异值将以与任何其他值相同的方式被覆盖。可解除引用的值和过去的值总是非单数的。

似乎至少似乎合理假设&#34;无效指针&#34;也意味着它是一个&#34;奇异迭代器&#34;的例子,但是没有文本来支持它;在相反的方向上,没有文本证实(同样似乎合理的)假设未初始化的指针值意味着是&#34;无效指针&#34;以及一个&#34;奇异的迭代器&#34;。因此,我们中间的分发者可能不接受&#34;大多数表达的结果都是未定义的&#34;澄清什么符合使用的无效指针。

C ++ 11在某种程度上更改了对应于3.7.2.3p4的文本:

  

[3.7.4.2p4] ...通过无效指针值间接并将无效指针值传递给释放函数具有未定义的行为。任何其他用途   无效指针值具有实现定义的行为。 [脚注:某些实现可能会定义复制无效指针值会导致系统生成的运行时错误。]

(省略号省略的文字没有改变)我们现在对于&#34;使用无效指针值&#34;的含义有了更清晰的说明,我们现在可以说OP&#39; s代码的语义肯定是实现定义的(但可能是实现定义为未定义)。迭代器的讨论中还有一个新的段落:

  

[24.2.1p10] 无效的迭代器是一个可能是单数的迭代器。

确认&#34;无效指针&#34;和#34;奇异迭代器&#34;实际上是一回事。 C ++ 11中剩下的混淆主要是关于生成无效/奇异指针/迭代器的确切情况;应该有一个指针/迭代器生命周期转换的详细图表(就像*值一样)。并且,与C ++ 98一样,标准是有缺陷的,因为它不保证这些值的复制和相等比较是有效的(未定义)。