c++删除指针,然后访问它所指向的值

c++ delete pointer and then access the value of it points to

本文关键字:访问 删除 指针 然后 c++      更新时间:2023-10-16

我刚刚在c++课上学习了指针和删除指针。我用自己的

试过这段代码
# include<iostream>
using namespace std;
int main(){
int num = 10;
int *p = new int;
p = &num;
cout << *p << endl;
delete p;
cout << num << endl;
return 0;
}

删除指针p后,无法打印num的值。但是如果我在程序的最后删除p,cout << num << endl;会给我10。有人知道我跑哪去了吗?

你首先泄露了一个指针

int *p = new int;
p = &num;  // You just leaked the above int

然后非法删除你没有删除的东西new

delete p;  // p points to num, which you did not new

您已经收到了几个指出错误的好答案,但是我读到关于堆变量与堆栈变量的分配和释放的更深层次的误解。

我意识到这已经成为一个相当长的帖子,所以如果人们认为它是有用的,我应该把它放在一个社区Wiki的某个地方。希望它能澄清你的一些困惑。

堆栈

堆栈是一个有限的固定大小的存储。如果您没有另行指定,将在这里创建局部变量,并且在不再需要它们时将自动清除它们。这意味着您不必显式地分配它们——它们将在您声明它们的那一刻开始存在。你也不需要释放它们——当它们超出作用域时,它们就会死亡,广义地说:当你到达定义它们的块的结束大括号时。

int main() {
int a; // variable a is born here
a = 3;
a++;
} // a goes out of scope and is destroyed here

指针指针只是一个变量,但不同于保存整数的int或保存真/假值的bool或保存浮点数的double,指针保存的是内存地址。您可以使用地址操作符&请求堆栈变量的地址:

{
int a = 3, b = 4;
int* p = &a; // p's value is the address of b, e.g. 0x89f2ec42
p = &b; // p now holds the address of b, e.g. 0x137f3ed0.
p++;    // p now points one address space further, e.g. 0x137f3ed4
cout << p; // Prints 0x137f3ed4
} // Variables a, b and p go out of scope and die

请注意,您不应该假设ab在内存中彼此"相邻",或者如果p有一个"已使用"地址作为其值,那么您也可以读取和写入p + 1的地址。

您可能知道,可以使用指针间接操作符访问地址处的值,例如

int* p = &a; // Assume similar as above
*p = 8; 
cout << a;   // prints 8
cout << &a << p; // prints the address of a twice.

请注意,即使我使用指针指向另一个变量,我也不需要清理任何东西:在某种意义上,p只是a的另一个名称,因为p和它指向的东西都被自动清理了,所以我在这里没有什么可做的。

堆内存是另一种类型的内存,理论上它的大小是无限的。您可以在这里创建变量,但需要显式地告诉c++您希望这样做。这样做的方法是通过调用new运算符,例如new int将在堆上创建一个整数并返回地址。您可以对分配的内存做一些合理的事情的唯一方法是保存它给您的地址。你这样做的方法是,将它存储在一个指针中:

int* heapPtr = new int;

,现在你可以使用指针访问内存:

*heapPtr = 3;
cout << heapPtr; // Will print the address of the allocated integer
cout << *heapPtr; // Will print the value at the address, i.e. 3
问题是,在堆上创建的变量将一直存在,直到你说你不再需要它们为止。您可以通过在要删除的地址上调用delete来做到这一点。例如,如果new给你0x12345678,内存将是你的,直到你调用delete 0x12345678。因此,在退出作用域之前,需要调用
delete heapPtr;

,您将告诉系统地址0x12345678对于下一个出现并需要堆上空间的代码再次可用。

内存泄露

现在这里有一个危险,那就是,你可能会失去把手。例如,考虑以下内容:

void f() {
int* p = new int;
}
int main() { 
f();
cout << "Uh oh...";
}

函数f在堆上创建一个新的整数。但是,存储地址的指针p是一个局部变量,一旦f退出,它就会被销毁。一旦你回到主函数,你突然有没有的想法了,你分配的整数在哪里,所以你没有办法再调用delete了。这意味着——至少在您的程序运行期间——根据您的操作系统,您将拥有被占用的内存,因此您不能将其用于其他任何事情。如果你这样做太频繁,你可能会耗尽内存,即使你不能访问它。

这是你犯的错误之一:

int* p = new int;

在堆上分配一个新的整数,并将地址存储在p中,但在下一行

p = &num;

用另一个地址覆盖它。此时,您将失去对堆上整数的跟踪,并且您已经创建了内存泄漏。

释放内存除了释放内存不够频繁(即不是一次)之外,您可能犯的另一个错误是过于频繁地释放内存。或者,更准确地说,您可以在告诉操作系统您不再需要它之后访问内存。例如,考虑以下内容:

int main() { 
int* p = new int;
*p = 10;
delete p;  // OK!
*p = 3;  // Errr...
}

最后一行非常错误!您刚刚返回了调用delete时分配的内存,但是地址仍然存储在p中。在调用delete之后,您的操作系统可以随时重新分配内存——例如,在另一个线程调用new double并获得相同的地址之后。在这一点上,如果你写*p = 3,你因此写的内存不再是你的这可能会导致灾难,如果你碰巧覆盖了内存中存储核弹发射代码的位置,或者什么都不会发生,因为内存在你的程序结束之前从来没有被用于其他任何事情。

永远释放你自己的记忆,除了你自己的记忆别无其他

我们得出如下结论:堆栈上分配的内存不是你要的,也不是你要释放的。堆上分配的内存是您的,但是您必须也必须释放一次且仅一次。

以下示例不正确:

{
int a = 3;
int* p = &a;
delete a;
} // Uh oh... cannot clean up a because it is not ours anymore!
{
int* p = new int;
delete p;
*p = 3;   // Uh oh, cannot touch this memory anymore!
delete p; // Uh oh, cannot touch this memory anymore!
}

为什么打印10?

嗯,老实说,你只是"幸运"而已。实际上,操作系统管理内存的方式通常是相当懒惰的。当你告诉它"我想要一些记忆"时,它不会帮你把它归零。这就是为什么写 是一个坏主意。
int main() {
int a;
a = a + 3;
cout << a;
}

你在内存的某个地方分配了一个变量a,但a的值将是该内存位置中的任何值。它可能是零,也可能是一些随机数,这取决于你启动电脑时比特的下落情况。这就是为什么您应该始终初始化变量:

int a = 0;

同样,当你说"我不再需要这个内存"时,操作系统不会将其归零。这将是缓慢且不必要的:它所需要做的就是将内存标记为"可以重新分配"。所以如果你把它还给它,然后马上访问它,它还没有被重新分配的概率是相当大的。因此

int* p = new int;
*p = 10;
delete p;
cout << *p;

不能保证打印10。p指向的地址可能已经(部分地)被紧跟在delete之后的其他人占用(并初始化!)。但是如果没有,内存中仍然会包含值10,所以即使它不再是你的,c++仍然允许你访问它。基本上,当你使用指针时,你是在告诉它"相信我,我是一个程序员——你不需要做各种缓慢的检查来确保我呆在我应该在的地方,相反,我会自己小心的!">

using namespace std;
int main(){
int num = 10;      // a) an int is created on stack
int *p = new int;  // b) another int is allocated on heap
p = &num;          // c) address of int from stack is assigned to p and the pointer 
//    allocated in b) is leaked: as nothing points to it anymore, 
//    it can't be deleted
cout << *p << endl; 
delete p;          // d) deleting a pointer that was not dynamically allocated
//    and is pointing to stack. 
cout << num << endl;
return 0;
}