为什么析构函数会无休止地调用自己(导致堆栈溢出)?
Why does the destructor call itself endlessly (causing a stack overflow)?
我很困惑为什么析构函数会无限次地调用自己,当我尝试通过静态函数调用 create_instance()
在堆上构造一个对象时说 LeakySingleton
然后尝试之后通过 delete
操作明确删除它。
据我了解,考虑到下面的源列表,main()
中的变量 leaky_singleton
指向由 create_instance()
返回的堆分配资源。这样,我们就通过create_instance
函数在堆上间接分配了一个对象LeakySingleton
。
现在,如果我在 leaky_singleton
上显式调用 delete 运算符或 delete 函数,那么它首先调用析构函数并检查它是否满足 instance != nullptr
条件,然后删除 [=22] 的对象=] 指向的内容应删除。
如果这个对象 LeakySingleton::instance
被删除,那么 dtor 就没有理由再次调用自己,或者我在这里遗漏了什么?
使用和不使用 valgrind 调用它都会导致分段错误(由于堆栈溢出导致内存访问无效):
Segmentation fault (core dumped)
单步执行调试器,导致无休止的析构函数调用(堆栈溢出的罪魁祸首)。
来自 cplusplus.com (http://www.cplusplus.com/forum/general/40044/):
If you delete your object, it attempts to delete itself, which will
cause it to attempt to delete itself, which will cause it to delete
itself, which will...
当我简单地使用 delete
operator/function 来释放静态 class 成员变量指向的堆对象 LeakySingleton
时,为什么它会尝试删除自身LeakySingleton::instance
?
堆分配的资源由指向 LeakySingleton
对象的 LeakySingleton::instance
指针变量指向。那么,为什么显式 delete
函数调用不会删除或释放已分配的堆对象,而是无休止地递归?我在这里错过了什么?
(我目前对dtor和ctor的理解:new
function/operator为堆上的对象分配内存并调用构造函数,delete
函数调用析构函数并在我的案例也在里面调用了一个delete
operator/function。)
来源:
main.cpp:
class Singleton final
{
public:
static Singleton & create_instance(int);
~Singleton() = default;
private:
int x;
Singleton(int);
Singleton(Singleton &) = delete;
Singleton(Singleton &&) = delete;
Singleton & operator=(Singleton &) = delete;
Singleton & operator=(Singleton &&) = delete;
};
Singleton::Singleton(int t_x) : x{t_x}
{}
Singleton & Singleton::create_instance(int t_x)
{
static Singleton instance{t_x};
return instance;
}
// Potential endless dtor calls inside:
class LeakySingleton final
{
public:
static LeakySingleton * create_instance(int);
~LeakySingleton();
private:
int x;
static LeakySingleton * instance;
LeakySingleton(int);
LeakySingleton(LeakySingleton &) = delete;
LeakySingleton(LeakySingleton &&) = delete;
LeakySingleton & operator=(LeakySingleton &) = delete;
LeakySingleton & operator=(LeakySingleton &&) = delete;
};
LeakySingleton * LeakySingleton::instance = nullptr;
LeakySingleton::LeakySingleton(int t_x) : x{t_x}
{}
LeakySingleton::~LeakySingleton()
{
if (instance != nullptr)
{
delete instance;
instance = nullptr;
}
}
LeakySingleton * LeakySingleton::create_instance(int t_x)
{
if (instance == nullptr)
{
instance = new LeakySingleton{t_x};
}
return instance;
}
int main()
{
// The correct implementation with no issues:
{
Singleton & singleton = Singleton::create_instance(42);
}
// The faulty implementation causing the dtor to recurse endlessly and resulting in a segfault:
{
LeakySingleton * leaky_singleton = LeakySingleton::create_instance(42);
delete leaky_singleton;
}
return 0;
}
生成文件:
CC = g++
CFLAGS = -g -Wall -Wextra -pedantic -std=c++11
SRC = main.cpp
TARGET = app
RM = rm -rf
.PHONY: all clean
all: $(TARGET)
clean:
$(RM) $(TARGET)
$(TARGET): $(SRC)
$(CC) $(CFLAGS) $^ -o $@
在析构函数中删除实例启动析构函数调用。
你有一个讨厌的周期,在 LeakySingleton::create_instance
你有:
instance = new LeakySingleton{t_x};
然后在 LeakySingleton
的析构函数中你有:
delete instance;
它将在您将任何内容设置为 null 之前调用 LeakySingleton
的析构函数:
instance = nullptr;
所以你有无限递归导致你的计算器溢出。
在 C++ 中,delete
将调用 class 析构函数。
main
函数中的 delete
语句正在调用 LeakySingleton::~LeakySingleton
,后者会尝试删除静态实例指针,然后再次调用析构函数。您的代码从来没有机会将静态指针设置为 null。你有一个无限循环。
P.S。恕我直言,在非静态方法中修改静态成员通常是一种不好的做法。我相信您可以将静态清理逻辑放在另一个静态方法中。
class LeakySingleton final {
public:
static LeakySingleton& create_instance(int);
static void destroy_instance();
~LeakySinglton() = default;
private:
static LeakySingleton *instance;
...
};
void LeakySingleton::destroy_instance() {
if (instance != nullptr) {
delete instance;
instance = nullptr;
}
}
首先,由于 LeakySingleton
不应直接创建,因此也不应直接销毁它:
- 因此它的析构函数应该是私有的,就像它的构造函数一样。
- 如果可以删除单例:应使用删除实例的 public 函数
delete_instance()
删除单例
- 析构函数不得删除自身(无限递归)
- 此构造应避免这种无休止的析构函数递归
如果您希望实例指针泄漏并允许其销毁,则不应执行两次(一次在析构函数外部,一次在析构函数内部),而只能在析构函数外部执行一次。由于只有一个实例,外部的析构函数意味着内部不需要删除调用:
LeakySingleton::~LeakySingleton()
{
if (instance != nullptr)
{
instance = nullptr; // since there's only one, it's the instance and
} // the instance pointer shall be reset
// and do what's needed to clean the object
}
注意:此实现不是线程安全的。
注 2:this article 您可能会感兴趣。它还针对 public 析构函数发出警告,因为这可能导致悬空指针。
我很困惑为什么析构函数会无限次地调用自己,当我尝试通过静态函数调用 create_instance()
在堆上构造一个对象时说 LeakySingleton
然后尝试之后通过 delete
操作明确删除它。
据我了解,考虑到下面的源列表,main()
中的变量 leaky_singleton
指向由 create_instance()
返回的堆分配资源。这样,我们就通过create_instance
函数在堆上间接分配了一个对象LeakySingleton
。
现在,如果我在 leaky_singleton
上显式调用 delete 运算符或 delete 函数,那么它首先调用析构函数并检查它是否满足 instance != nullptr
条件,然后删除 [=22] 的对象=] 指向的内容应删除。
如果这个对象 LeakySingleton::instance
被删除,那么 dtor 就没有理由再次调用自己,或者我在这里遗漏了什么?
使用和不使用 valgrind 调用它都会导致分段错误(由于堆栈溢出导致内存访问无效):
Segmentation fault (core dumped)
单步执行调试器,导致无休止的析构函数调用(堆栈溢出的罪魁祸首)。
来自 cplusplus.com (http://www.cplusplus.com/forum/general/40044/):
If you delete your object, it attempts to delete itself, which will cause it to attempt to delete itself, which will cause it to delete itself, which will...
当我简单地使用 delete
operator/function 来释放静态 class 成员变量指向的堆对象 LeakySingleton
时,为什么它会尝试删除自身LeakySingleton::instance
?
堆分配的资源由指向 LeakySingleton
对象的 LeakySingleton::instance
指针变量指向。那么,为什么显式 delete
函数调用不会删除或释放已分配的堆对象,而是无休止地递归?我在这里错过了什么?
(我目前对dtor和ctor的理解:new
function/operator为堆上的对象分配内存并调用构造函数,delete
函数调用析构函数并在我的案例也在里面调用了一个delete
operator/function。)
来源:
main.cpp:
class Singleton final
{
public:
static Singleton & create_instance(int);
~Singleton() = default;
private:
int x;
Singleton(int);
Singleton(Singleton &) = delete;
Singleton(Singleton &&) = delete;
Singleton & operator=(Singleton &) = delete;
Singleton & operator=(Singleton &&) = delete;
};
Singleton::Singleton(int t_x) : x{t_x}
{}
Singleton & Singleton::create_instance(int t_x)
{
static Singleton instance{t_x};
return instance;
}
// Potential endless dtor calls inside:
class LeakySingleton final
{
public:
static LeakySingleton * create_instance(int);
~LeakySingleton();
private:
int x;
static LeakySingleton * instance;
LeakySingleton(int);
LeakySingleton(LeakySingleton &) = delete;
LeakySingleton(LeakySingleton &&) = delete;
LeakySingleton & operator=(LeakySingleton &) = delete;
LeakySingleton & operator=(LeakySingleton &&) = delete;
};
LeakySingleton * LeakySingleton::instance = nullptr;
LeakySingleton::LeakySingleton(int t_x) : x{t_x}
{}
LeakySingleton::~LeakySingleton()
{
if (instance != nullptr)
{
delete instance;
instance = nullptr;
}
}
LeakySingleton * LeakySingleton::create_instance(int t_x)
{
if (instance == nullptr)
{
instance = new LeakySingleton{t_x};
}
return instance;
}
int main()
{
// The correct implementation with no issues:
{
Singleton & singleton = Singleton::create_instance(42);
}
// The faulty implementation causing the dtor to recurse endlessly and resulting in a segfault:
{
LeakySingleton * leaky_singleton = LeakySingleton::create_instance(42);
delete leaky_singleton;
}
return 0;
}
生成文件:
CC = g++
CFLAGS = -g -Wall -Wextra -pedantic -std=c++11
SRC = main.cpp
TARGET = app
RM = rm -rf
.PHONY: all clean
all: $(TARGET)
clean:
$(RM) $(TARGET)
$(TARGET): $(SRC)
$(CC) $(CFLAGS) $^ -o $@
在析构函数中删除实例启动析构函数调用。
你有一个讨厌的周期,在 LeakySingleton::create_instance
你有:
instance = new LeakySingleton{t_x};
然后在 LeakySingleton
的析构函数中你有:
delete instance;
它将在您将任何内容设置为 null 之前调用 LeakySingleton
的析构函数:
instance = nullptr;
所以你有无限递归导致你的计算器溢出。
在 C++ 中,delete
将调用 class 析构函数。
main
函数中的 delete
语句正在调用 LeakySingleton::~LeakySingleton
,后者会尝试删除静态实例指针,然后再次调用析构函数。您的代码从来没有机会将静态指针设置为 null。你有一个无限循环。
P.S。恕我直言,在非静态方法中修改静态成员通常是一种不好的做法。我相信您可以将静态清理逻辑放在另一个静态方法中。
class LeakySingleton final {
public:
static LeakySingleton& create_instance(int);
static void destroy_instance();
~LeakySinglton() = default;
private:
static LeakySingleton *instance;
...
};
void LeakySingleton::destroy_instance() {
if (instance != nullptr) {
delete instance;
instance = nullptr;
}
}
首先,由于 LeakySingleton
不应直接创建,因此也不应直接销毁它:
- 因此它的析构函数应该是私有的,就像它的构造函数一样。
- 如果可以删除单例:应使用删除实例的 public 函数
delete_instance()
删除单例 - 析构函数不得删除自身(无限递归)
- 此构造应避免这种无休止的析构函数递归
如果您希望实例指针泄漏并允许其销毁,则不应执行两次(一次在析构函数外部,一次在析构函数内部),而只能在析构函数外部执行一次。由于只有一个实例,外部的析构函数意味着内部不需要删除调用:
LeakySingleton::~LeakySingleton()
{
if (instance != nullptr)
{
instance = nullptr; // since there's only one, it's the instance and
} // the instance pointer shall be reset
// and do what's needed to clean the object
}
注意:此实现不是线程安全的。
注 2:this article 您可能会感兴趣。它还针对 public 析构函数发出警告,因为这可能导致悬空指针。