指针和动态内存分配
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";
我自己写这个纯粹是为了理解指针并理解(也迷失在)动态new
和delete
。
我的 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?
再次提前感谢大家。
耀峰
【问题讨论】:
您很“幸运”,在您的第二个代码示例中p
是 0
。由于它是未初始化的,它可以有任何值。因为它是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
。它
可以是任何东西。
您可以删除0
或NULL
而不会造成任何伤害。
未定义的行为是一种可能发生任何事情的行为。这
程序可能会崩溃,它可能会像什么都没发生一样正常工作,
它可能会产生一些随机结果等,
但是,我在上面的程序中对 p 调用了 delete 并且它编译了。
谁能给我解释一下?为什么我可以删除 p?
那是因为你通过x
分配了new
分配的内存地址。 (p = x;
)x
(或p
)是一个有效的内存位置,可以删除。
现在x
被称为Dangling pointer。因为它指向一个不再有效的内存。删除后访问x
是未定义的行为。
谁能解释一下?为什么我可以删除 p,因为它与 x 无关?
这是因为您的 p
被分配了 0。因此您正在摆脱未定义的行为。但是,不能保证您的未初始化指针的值为0
或NULL
。在这一点上,它似乎工作正常,但您在这里遇到了未定义的行为。
【讨论】:
非常感谢您分享有关悬空指针的信息。【参考方案5】:delete/free 关键字用于从内存位置清空存储的值。如果我们不使用指针,我们可以将值重新分配为 NULL 或者让它们超出范围。
小心使用指针,如果我们在不删除值的情况下使用指针超出范围。它会造成内存泄漏。因为那部分内存块不再可用。而且我们丢失了地址,因为我们在不同的范围内。
【讨论】:
以上是关于指针和动态内存分配的主要内容,如果未能解决你的问题,请参考以下文章