关于C++中的动态内存分配
Regarding dynamic memory allocation in C++
假设我有如下通过p1
的动态内存分配,
int* p1 = new int;
*p1 = 1;
我知道 p1
引用的内存可以使用
释放
delete p1;
p1 = nullptr;
但是我想知道是否有另一个指针p2
指向1
,我可以delete
这个指针来释放内存吗?指针 p1
会发生什么?另外,p1
和p2
本质上是什么关系?例如,
int* p1 = new int;
*p1 = 1;
int* p2 = p1;
// Can I delete p2 like this? And what would happen to p1?
delete p2;
p2 = nullptr;
您可以删除 p2
,但取消引用 p1
将导致未定义的行为,并可能导致分段错误。
它是这样工作的:
- 在某个地址分配了内存。
p1
和 p2
都指向这个内存位置。
- 一旦
p2
删除 - p1
仍指向此内存位置。
没有泄漏,一切都很好 - 只是不要取消引用 p1
。你可以自由p1 = nullptr
,但不能*p1 = 1
。另外,你不能删除 p1
,因为它已经被删除了,你可能会遇到段错误。
您可以删除 p1
或 p2
。不会有任何区别。但是你不应该同时删除两者。另外,一旦你删除了一个,你就不应该使用另一个。程序员对此负责。语言本身不会提供任何帮助。这里有很多不同的方法可以写出糟糕的代码。
有几个 techniques/patterns 可以处理这个问题。智能指针经常用于此。查看 std::shared_ptr 文档。不要使用过时的 auto_ptr
.
我最喜欢的图案是 "ownership"。这意味着一个指针 "owns" 分配而所有其他指针只使用。这在编程时需要一定的纪律,但是一旦应用了这种努力,生成的代码就会清晰简单。例如:
class MyClass
{
public: ~MyClass() { for(char *p: myStringsDict) delete p; }
private:
std::unordered_set<char*> myStringsDict;
};
看看这个 class 很明显(尽管添加适当的注释会很好)它拥有一个字符串字典,并且只要这个 [=30 的实例,这些字符串就有效=] 存在。这些指针可以用在这个class拥有的结构中,它们可以作为参数传递给函数等。什么时候不应该再使用它们就很清楚了。
在多线程的服务器编程中运行,双重删除非常危险且难以追踪。因为在删除第一个指针后,内存变得空闲并且可以在不同的线程上分配用于其他目的。当第二个指针被释放时,它可能会删除一个有效的分配,而其他代码段对此一无所知并继续使用这块内存。
所有这些问题真正好的解决方案是垃圾收集。当使用显式分配时,程序员需要以这种或那种方式付出额外的努力。
And what would happen to pointer p1? In addition, what is the relationship between p1 and p2 essentially?
它们的本质关系是指的是赋值后动态内存分配得到的同一个地址int* p2 = p1;
.
所以删除其中任何一个都将释放分配的内存。将其中之一设置为 nullptr
不会影响另一个。
所以你留下了一个无法安全删除的悬空指针。
您描述的是(旧的)C++ 中一个众所周知的问题:当多个指针指向同一个动态内存时,哪个指针会删除它?
如果你同时删除 p1
和 p2
你会双倍删除内存,如果你删除 p1
或 p2
并且您继续通过另一个指针使用内存 - 您正在使用悬空指针,这是未定义的行为(在最好的情况下是崩溃)。
你需要确保当一个指针被删除时 - 你不会在其他指针中使用该内存。
C++11 引入了处理这个问题的标准方法:使用自计数指针,只有最后一个指针会删除内存:
auto p1 = std::make_shared<int>(0);
auto p2 = p1;
现在,最后一个存活的指针将删除分配的内存,您完全不必担心谁在删除什么。
让我们探索房地产类比,内存扮演着土地的角色,而指针扮演着地址的角色,这并不奇怪。
指针变量是黄色的post-it 注释。你可以在上面写一个街道地址。从自由存储区分配的变量是某个地址的一块土地。
int *p = new int;
你要求城市在某处找到一小块未使用的土地并将所有权分配给你自己。你在黄色便条上写下它的街道地址。
*p = 1;
你在那个地址盖了一栋整洁的小房子。
int *q = p;
您复印了黄色便条。过一段时间就忘了。
delete p;
你拆了房子,放弃了你对那块地的权利。市政府可能会将其分配给其他人。也许有人想在那里再盖一座小楼,或者铺设铁轨或设立鲨鱼池。请注意,这对您的任何黄色笔记没有任何影响。
p = nullptr;
你把一张黄色的纸条擦干净了。你的另一张黄色便条还在。
*q = 2;
您找到另一张黄色便条,从上面读出地址并假设这块土地是您的。不好的举动。你继续在别人的土地上建造一座整洁的小房子。新主人根本不在乎(他们无从知晓)。明天他们可能会拆除你的建筑物并建造他们自己的建筑物,或者用火车碾压你,或者可能向你倾倒 100000 吨水和 3 个 makos。那是相当不愉快!不要碰不属于你的东西。
当使用 new 分配动态内存时,它应该通过 delete 释放,只要您使用 new 创建 p1 然后使用 delete 释放它。
你将 p2 声明为指向同一内存的指针p2.
如果你在 p1 上调用 delete 然后让 p2 指向 null 以避免错误地取消引用它因为写:
delete p1;
*p2 = 1;
将导致未定义的行为。
假设我有如下通过p1
的动态内存分配,
int* p1 = new int;
*p1 = 1;
我知道 p1
引用的内存可以使用
delete p1;
p1 = nullptr;
但是我想知道是否有另一个指针p2
指向1
,我可以delete
这个指针来释放内存吗?指针 p1
会发生什么?另外,p1
和p2
本质上是什么关系?例如,
int* p1 = new int;
*p1 = 1;
int* p2 = p1;
// Can I delete p2 like this? And what would happen to p1?
delete p2;
p2 = nullptr;
您可以删除 p2
,但取消引用 p1
将导致未定义的行为,并可能导致分段错误。
它是这样工作的:
- 在某个地址分配了内存。
p1
和p2
都指向这个内存位置。- 一旦
p2
删除 -p1
仍指向此内存位置。 没有泄漏,一切都很好 - 只是不要取消引用p1
。你可以自由p1 = nullptr
,但不能*p1 = 1
。另外,你不能删除p1
,因为它已经被删除了,你可能会遇到段错误。
您可以删除 p1
或 p2
。不会有任何区别。但是你不应该同时删除两者。另外,一旦你删除了一个,你就不应该使用另一个。程序员对此负责。语言本身不会提供任何帮助。这里有很多不同的方法可以写出糟糕的代码。
有几个 techniques/patterns 可以处理这个问题。智能指针经常用于此。查看 std::shared_ptr 文档。不要使用过时的 auto_ptr
.
我最喜欢的图案是 "ownership"。这意味着一个指针 "owns" 分配而所有其他指针只使用。这在编程时需要一定的纪律,但是一旦应用了这种努力,生成的代码就会清晰简单。例如:
class MyClass
{
public: ~MyClass() { for(char *p: myStringsDict) delete p; }
private:
std::unordered_set<char*> myStringsDict;
};
看看这个 class 很明显(尽管添加适当的注释会很好)它拥有一个字符串字典,并且只要这个 [=30 的实例,这些字符串就有效=] 存在。这些指针可以用在这个class拥有的结构中,它们可以作为参数传递给函数等。什么时候不应该再使用它们就很清楚了。
在多线程的服务器编程中运行,双重删除非常危险且难以追踪。因为在删除第一个指针后,内存变得空闲并且可以在不同的线程上分配用于其他目的。当第二个指针被释放时,它可能会删除一个有效的分配,而其他代码段对此一无所知并继续使用这块内存。
所有这些问题真正好的解决方案是垃圾收集。当使用显式分配时,程序员需要以这种或那种方式付出额外的努力。
And what would happen to pointer p1? In addition, what is the relationship between p1 and p2 essentially?
它们的本质关系是指的是赋值后动态内存分配得到的同一个地址int* p2 = p1;
.
所以删除其中任何一个都将释放分配的内存。将其中之一设置为 nullptr
不会影响另一个。
所以你留下了一个无法安全删除的悬空指针。
您描述的是(旧的)C++ 中一个众所周知的问题:当多个指针指向同一个动态内存时,哪个指针会删除它?
如果你同时删除 p1
和 p2
你会双倍删除内存,如果你删除 p1
或 p2
并且您继续通过另一个指针使用内存 - 您正在使用悬空指针,这是未定义的行为(在最好的情况下是崩溃)。
你需要确保当一个指针被删除时 - 你不会在其他指针中使用该内存。
C++11 引入了处理这个问题的标准方法:使用自计数指针,只有最后一个指针会删除内存:
auto p1 = std::make_shared<int>(0);
auto p2 = p1;
现在,最后一个存活的指针将删除分配的内存,您完全不必担心谁在删除什么。
让我们探索房地产类比,内存扮演着土地的角色,而指针扮演着地址的角色,这并不奇怪。
指针变量是黄色的post-it 注释。你可以在上面写一个街道地址。从自由存储区分配的变量是某个地址的一块土地。
int *p = new int;
你要求城市在某处找到一小块未使用的土地并将所有权分配给你自己。你在黄色便条上写下它的街道地址。
*p = 1;
你在那个地址盖了一栋整洁的小房子。
int *q = p;
您复印了黄色便条。过一段时间就忘了。
delete p;
你拆了房子,放弃了你对那块地的权利。市政府可能会将其分配给其他人。也许有人想在那里再盖一座小楼,或者铺设铁轨或设立鲨鱼池。请注意,这对您的任何黄色笔记没有任何影响。
p = nullptr;
你把一张黄色的纸条擦干净了。你的另一张黄色便条还在。
*q = 2;
您找到另一张黄色便条,从上面读出地址并假设这块土地是您的。不好的举动。你继续在别人的土地上建造一座整洁的小房子。新主人根本不在乎(他们无从知晓)。明天他们可能会拆除你的建筑物并建造他们自己的建筑物,或者用火车碾压你,或者可能向你倾倒 100000 吨水和 3 个 makos。那是相当不愉快!不要碰不属于你的东西。
当使用 new 分配动态内存时,它应该通过 delete 释放,只要您使用 new 创建 p1 然后使用 delete 释放它。 你将 p2 声明为指向同一内存的指针p2.
如果你在 p1 上调用 delete 然后让 p2 指向 null 以避免错误地取消引用它因为写:
delete p1;
*p2 = 1;
将导致未定义的行为。