指针和动态内存分配

Posted

技术标签:

【中文标题】指针和动态内存分配【英文标题】:pointer and dynamic memory allocation 【发布时间】:2015-02-05 20:41:30 【问题描述】:

我的问题:

int* x = new int;
cout << x<<"\n";
int* p;
cout << p <<"\n";
p = x;
delete p;
cout << p <<"\n";

我自己写这个纯粹是为了理解指针并理解(也迷失在)动态newdelete

我的 XCode 可以编译程序并返回以下结果:

 0x100104250
 0x0
0x100104250

我知道我只能在动态分配的内存上调用 delete。 但是,我在上面的程序中对p 调用了delete,它编译了。

谁能给我解释一下? 为什么我可以删除p

另外,我发现如果程序改成下面这样:

 int* x = new int;
 int* p;
 cout << p <<"\n";
 delete p;
 cout << p <<"\n";

然后我的 Xcode 再次编译并返回我:

 0x0
 0x0
 Program ended with exit code: 0

现在,我完全迷路了:(。 谁能给我解释一下? 为什么我可以删除p,因为它与x 无关?


由于 Xcode 编译成功,我假设以上两个程序在计算机上是正确的。 但是,我认为这又是“仅在动态分配的内存上调用删除”的声明。 或者可能,我没有完全理解什么是指针,什么是动态分配的内存。 我在网上搜索时发现了这个post。 但我不认为它像我的情况。

请帮帮我。


我想再问一个问题。关于二叉搜索树的代码是here。 从第 28 行到第 32 行,它处理删除具有一个子节点的节点。 我把这部分代码放在这里,以防网页链接失效。

else if(root->left == NULL) 结构节点 *temp = root; 根=根->对; 删除温度;

正是这些代码引导我提出上述关于指针的问题。 按照这篇文章给出的答案。 下面这样理解代码是否正确?

我不能首先将根的父节点链接到根的右子节点。 然后删除根节点,因为根节点下面的子树也会被删除。 所以我必须创建一个临时指针,指向内存槽,也就是root所指向的内存槽。 然后我将根的父节点链接到根的右子节点。 现在,我可以安全地删除“root”指向的内存插槽(即 temp,因为它们都指向同一个内存)。 这样,我释放了内存,也保持了父母和孩子之间的联系。 此外,温度仍然存在并且仍然指向“那个”内存插槽。 删除后是否应该设置为NULL?

再次提前感谢大家。

耀峰

【问题讨论】:

您很“幸运”,在您的第二个代码示例中 p0。由于它是未初始化的,它可以有任何值。因为它是0(又名NULL),所以调用delete 是有效的(这有助于避免对NULL 进行一百万次检查,特别是在处理分配失败的错误情况时,并且您想要清理其余的分配——如果所有的指针都先初始化为NULL,那么你可以只用delete所有的东西,不用担心哪个分配失败)。 只是一个建议,你总是应该初始化你的指针变量,比如 int *p = 0;或 int *p = NULL;那是因为在调试版本中,这将为您完成。但在发布版本中,它不会完成。这可以为您节省大量调试时间。 @user743414 除非您要维护旧代码,否则您应该使用 C++11,因此应该使用 int *p = nullptr;。 (这部分)C++11 多年来一直被所有主要编译器支持。 @Angew 好吧。我不知道。 :) 看来我学到了新东西。 您可能想看看***.com/a/6445794/1382251。虽然是指局部变量的生命周期,但概念是一样的。 【参考方案1】:

是的,您只能在通过new 分配的内存上调用delete。请注意,重要的是内存的地址(指针的),而不是存储指针的变量。所以,你的第一个代码:

int* x = new int; //(A)
cout << x<<"\n";
int* p;
cout << p <<"\n";
p = x;  //(B)
delete p;  //(C)
cout << p <<"\n"; //(D)

行 (A) 在某个地址(示例输出中的0x100104250)动态分配内存,并将该地址存储在变量x 中。内存是通过new分配的,这意味着delete最终必须在地址0x100104250上调用。

行 (B) 将地址 0x100104250(指针 x 的值)分配给指针 p。 (C) 行然后调用delete p,这意味着“释放p 指向的内存”。这意味着它在地址0x100104250 上调用delete,一切都很好。地址0x100104250 的内存 是通过new 分配的,因此没有通过delete 正确分配。您使用不同的变量来存储值这一事实没有任何作用。

行 (D) 只是打印出指针的值。 delete 作用于指针指向的内存,而不是指针本身。指针的值保持不变 - 它仍然指向相同的内存,只是不再分配内存。


第二个例子不同——当p 没有被初始化为任何东西时,你正在调用delete p。它指向一块随机的内存,因此在其上调用delete当然是非法的(从技术上讲,它具有“未定义的行为”,并且很可能会崩溃)。

似乎在您的特定示例中,您正在运行调试构建,并且如果您不自己初始化局部变量,那么您的编译器会“有帮助地”将局部变量初始化为 0。因此,它实际上会导致您的第二个示例不会崩溃,因为在空指针上调用 delete 是有效的(并且什么都不做)。但是程序实际上有一个错误,因为局部变量通常不会被隐式初始化。

【讨论】:

非常感谢您详细及时的答复。这让我对指针感觉舒服多了。【参考方案2】:
p = x;

这将使p 包含与x 相同的值(p 将指向与x 相同的对象)。所以基本上当你说

delete p;

它对p引用的地址进行删除,与x的地址相同。因此,这是完全有效的,因为该地址引用的对象是使用 new 分配的。

第二种情况:-

巧合的是,您的指针 p 被编译器设置为 NULL pointer(您不应该依赖它)。所以删除那个指针是安全的。如果不是 NULL 指针,您可能会看到崩溃。

【讨论】:

是的,它有时会崩溃。我在尝试不同的代码时发现了它。我忘了把它包括在我的问题中。非常感谢您向我确认此信息。【参考方案3】:

好的,让我们看一下“删除”运算符的文档。根据http://en.cppreference.com/w/cpp/memory/new/operator_delete:

由删除表达式调用以释放先前为单个对象分配的存储空间。此函数的标准库实现的行为是未定义的,除非 ptr 是空指针或者是先前从 operator new(size_t) 或 operator new(size_t, std::nothrow_t) 的标准库实现中获得的指针。

所以会发生以下情况:您正在调用 new 运算符,它为内存中的 int 变量分配 sizeof(int) 个字节。内存中的那部分由您的指针 x 引用。然后创建另一个指针 p,它指向相同的内存地址。当你调用 delete 时,内存被释放。 p 和 x 仍然指向同一个内存 address,只是那个位置的 value 现在是垃圾。为了使这更容易理解,我将您的代码修改如下:

#include <iostream>
using namespace std;

int main() 
    int * x = new int;               // Allocate sizeof(int) bytes, which x references to
    *x = 8;                          // Store an 8 at the newly created storage
    cout << x << " " << *x << "\n";  // Shows the memory address x points to and "8". (e.g. 0x21f6010 8)
    int * p;                         // Create a new pointer
    p = x;                           // p points to the same memory location as x
    cout << p << " " << *p << "\n";  // Shows the memory address p points to (the same as x) and "8".
    delete p;                        // Release the allocated memory
    cout <<  x << " " <<  p << " "
         << *x << " " << *p << "\n"; // p now points to the same memory address as before, except that it now contains garbage (e.g. 0x21f6010 0)
    return 0;

运行后得到如下结果:

0x215c010 8
0x215c010 8
0x215c010 0x215c010 0 0

所以请记住,使用 delete 可以释放内存,但指针仍然指向相同的地址。这就是为什么在之后也将指针设置为 NULL 通常是一种安全的做法。希望这现在更有意义:-)

【讨论】:

对我帮助很大。虽然我的 Xcode 最后返回 8 8 而不是 0 0。但我想我明白了要点。【参考方案4】:

在回答之前,你需要了解以下几点。

    一旦你delete 一个指针,它需要不被分配0。它 可以是任何东西。 您可以删除0NULL 而不会造成任何伤害。 未定义的行为是一种可能发生任何事情的行为。这 程序可能会崩溃,它可能会像什么都没发生一样正常工作, 它可能会产生一些随机结果等,

但是,我在上面的程序中对 p 调用了 delete 并且它编译了。

谁能给我解释一下?为什么我可以删除 p?

那是因为你通过x分配了new分配的内存地址。 (p = x;x(或p)是一个有效的内存位置,可以删除。

现在x 被称为Dangling pointer。因为它指向一个不再有效的内存。删除后访问x 是未定义的行为。

谁能解释一下?为什么我可以删除 p,因为它与 x 无关?

这是因为您的 p 被分配了 0。因此您正在摆脱未定义的行为。但是,不能保证您的未初始化指针的值为0NULL。在这一点上,它似乎工作正常,但您在这里遇到了未定义的行为。

【讨论】:

非常感谢您分享有关悬空指针的信息。【参考方案5】:

delete/free 关键字用于从内存位置清空存储的值。如果我们不使用指针,我们可以将值重新分配为 NULL 或者让它们超出范围。

小心使用指针,如果我们在不删除值的情况下使用指针超出范围。它会造成内存泄漏。因为那部分内存块不再可用。而且我们丢失了地址,因为我们在不同的范围内。

【讨论】:

以上是关于指针和动态内存分配的主要内容,如果未能解决你的问题,请参考以下文章

指针和动态内存分配

C和C指针小记(十六)-动态内存分配

c语言中啥是动态分配内存?

指针做参数的动态内存分配与二重指针(上)

动态内存1(动态内存与智能指针)

动态内存——动态内存与智能指针