又是"pointer being freed was not allocated"

Yet Another "pointer being freed was not allocated"

本文关键字:not allocated was being pointer 又是 freed      更新时间:2023-10-16

好的,我知道有大量关于此错误的帖子,但我找不到一个简单的例子和对问题的清晰解释。免责声明:我发誓这不是作业:)我只是在玩智能指针,弄清楚它们,这发生了。

shared_ptr<string> a = make_shared<string>("This is a string.");
shared_ptr<string> b = make_shared<string>("This is b string.");
cout << a.use_count() << " " << b.use_count() << endl;
a.reset(b.get());
cout << a.use_count() << " " << b.use_count() << endl;

在此代码之后,我希望 a 指向 b(情况确实如此(,并且 b 的使用计数为 2。相反,我得到以下输出:

1 1
1 1

很公平,我想。毕竟,我正在传递一个原始指针,编译器/运行时应该如何知道其关联的控制块,对吧?右?!(好吧,我真的希望编译器能够弄清楚这一点,但我想这是一个简单的案例,如果没有发生这种情况,一定有充分的理由(

继续,我有以下代码

cout << a << " " << *a << endl;
cout << b << " " << *b << endl;

很好地打印出来

0x7f8f10403288 This is b string.
0x7f8f10403288 This is b string.

好的,这正是我所期望的:a 现在指向 b。但是,紧接着,我有一个返回 0 并且我退出了 main((,而输出喊出以下内容

tests(62775,0x7fff7940d000) malloc: *** error for object 0x7f8f10403288: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug

现在我很困惑:确切地说,没有分配哪个被释放的指针?我的钱会在 a 或 b 上,因为它们指向同一件事,自然它们超出了范围,因此它们的删除在同一个指针上被调用两次。我说的对吗?我错过了什么吗?还有什么我绝对应该知道的吗?

编辑我尝试了哑指针等效项

string* a = new string("This is a string");
string* b = new string("This is b string");
a = b;
cout << *a << endl << *b << endl;

这次代码没有窒息。那么,围绕智能指针有什么特别的事情发生吗?

您永远不应该从智能指针中get原始指针并将其传递给另一种智能指针类型,就像您所做的那样(使用 a.reset(b.get()); (。

发生了什么事情:

发生的事情是,智能指针ab的两个"所有者组"都在尝试管理相同的原始指针。将原始指针传递给 reset 函数时,shared_ptr a假定它正在获取一个无主指针,因此它将管理原始指针的生存期。删除所有权组中的最后一shared_ptr时,将取消分配关联的内存。

问题是b删除字符串,然后a在字符串被破坏时再次尝试删除字符串。切勿尝试两次释放相同的内存。否则,行为是未定义的,您可能会遇到异常,或者您可能会覆盖不应该覆盖的内存(因为它已被重新分配(,这可能会导致程序的不相关部分出现各种错误。

当然,这发生在 return 语句之后,因为那是智能指针a并且b超出范围(在堆栈上(并被破坏的时候。

如何将一个shared_ptr分配给另一个:

要分配共享指针,您只需说:

a = b;

这将自动取消分配a指向的任何内容,并将其设置为 b ,但智能指针将知道它们都指向同一个对象,而不是尝试独立管理同一字符串的生存期。

基本上,共享指针的operator=(以及复制构造函数(创建属于同一组(共享相同的引用计数(的新共享指针。因此,两个共享指针的use_count()将返回2(因为它们知道彼此(,并且只有在最后一个共享指针被破坏时才会释放内存。

reset 函数需要一个指向新分配对象的指针,而不是已由另一个共享指针管理的指针。所以你的怀疑是正确的。 ab都认为他们是指针的唯一所有者。因此,当它们超出范围时,它们都在各自的析构函数中的同一指针上调用 delete。如果您希望ab共享所有权,只需使用 shared_ptr 赋值运算符。

a = b;

b 拥有的指针被释放了两倍a,并且b超出范围,因为ab分别对该指针的使用计数为 1。

你的哑指针等效物根本不释放任何东西,所以它不会崩溃,但如果你在 valgrind 下运行它,你会看到它泄漏。

顺便说一句,库确实没有任何好方法来确定指针是否已由另一个智能指针对象拥有。(这不是编译器的工作---编译器只是翻译代码。控制块不一定靠近对象。即使是这样,如果它不存在,也可能触发分段错误。也许shared_ptr可以维护一个拥有指针的全局哈希表,但这会产生太多开销。因此,您必须明智地编写代码。切勿调用 .get()并将原始指针传递给将获得所有权的函数。

您需要

了解shared_ptrmake_shared是库功能,而不是编译器的功能。

编译器不检查提供它们所包含的指针的上下文 - 库函数的行为在标准中指定。 实际上,这意味着行为在库的源代码中是硬编码的,并且编译器不会根据使用上下文进行更改。

get()成员仅返回一个原始指针。 它不会导致包含对象以某种方式注册该对象可能由另一个shared_ptr管理。

另一方面,reset()成员假定它正在接收无主指针,并声明所有权。

正因为如此a.reset(b.get())导致ab都相信他们拥有同一个指针并对其生命周期负责,而谁也不知道对方声称拥有所有权。 销毁a时,它会删除该指针。 b也是如此. 净效果是删除同一对象两次。 这是不确定的行为。

使用a = b进行ab共同共享所有权。

"智能指针周围有什么特别的事情发生吗?" -- 是的! 这就是智能指针与原始指针的区别。 智能指针隐式且自动地执行某些特殊操作:

  • 自动与其他智能指针共享内存
  • 跟踪并在没有人引用内存时自动释放内存

这是在标准库中通过特定于类的复制操作和析构函数的魔力实现的。 这些是特殊函数,当某些事件发生时,它们会被调用 - 就像魔法一样。

使用a=b是在两个智能指针之间共享所有权的正确方法,但它有点令人费解。 C++中的典型赋值操作使用称为值类型语义的方法。 这意味着被分配对象的整个值实际上是从一个地方复制到另一个地方 - 或者至少系统使它看起来像这样。 然而,对于智能指针来说,情况就不同了。 智能指针实现引用类型的概念。 这意味着赋值只传输引用并更新引用计数 - 并且这两个变量随后将引用相同的内存对象。

通常,您不希望在共享指针上调用get()。 这是一个低级功能,专为做杂乱事情的人而设计。 get()返回一个原始指针并将控制权交给程序员 - 智能指针的所有特殊功能都被绕过。 当你使用get()时,你基本上是在告诉图书馆你知道你在做什么——而且你会小心。 如果事实证明并非如此,则会出现丑陋的运行时错误,例如 pointer being freed was not allocated .