堆内存中的 C++ 向量导致分段错误
Posted
技术标签:
【中文标题】堆内存中的 C++ 向量导致分段错误【英文标题】:C++ Vector in Heap Memory results in Segmentation Fault 【发布时间】:2020-09-23 16:31:23 【问题描述】:我是 C++ 新手,我相信这个问题一定很容易处理,但我想我错过了一些主要概念。在代码中,实现了一个二叉树,并要求我们确定该二叉树中的节点数。正如您在 Node 类中看到的,一个 Node 有两个成员指针:left 指向左侧的 Node,right 指向右侧的 Node。输入是根节点。无论如何,我找到了这样的解决方案:
-
制作一个节点向量,我在代码中命名为nodes,并附加根节点,并将节点数(代码中的howManyNodes)设置为1。
当节点不为空时(一开始我们添加根节点),检查它是否有一个 left 和一个 right 指针。如果它们可用(即不是 nullptr),请将这些指针添加到节点向量(我使用 insert)。同时将节点数(代码中的howManyNodes)加一。
检查特定节点是否具有左右指针后,我从列表中删除该节点,为此我使用 pop_back() 函数。
最后,向量将为空,我将获得 howManyNodes 作为答案。
这里是代码,我只实现了count函数。其余的来自模板。
#include <iostream>
#include <vector>
class Node
public:
Node *left, *right;
Node() left = right = nullptr;
~Node()
delete left;
left = nullptr;
delete right;
right = nullptr;
;
// I implement this part
int count(Node *n)
int howManyNodes = 1;
std::vector<Node> *nodes = new std::vector<Node>;
nodes->push_back(*n);
while (!nodes->empty())
Node* trial = new Node(nodes->back());
if (trial->left)
std::cout << trial->left << std::endl;
nodes->insert(nodes->begin(), *trial->left);
howManyNodes += 1;
if (trial->right)
std::cout << trial->right << std::endl;
nodes->insert(nodes->begin(), *trial->right);
howManyNodes += 1;
nodes->pop_back();
return howManyNodes;
// I implement this part.
int main()
Node *n = new Node();
n->left = new Node();
n->right = new Node();
n->right->left = new Node();
n->right->right = new Node();
n->right->right->right = new Node();
// This should print a count of six nodes
std::cout << count(n) << std::endl;
// Deleting n is sufficient to delete the entire tree
// because this will trigger the recursively-defined
// destructor of the Node class.
delete n;
n = nullptr;
return 0;
问题是,我永远无法摆脱分段错误。正如我搜索的那样,当代码试图访问它不应该访问的内存时会发生这种情况。我的代码可能很容易修复,但我有以下问题:
-
如果 std::vector 为其成员使用堆内存,为什么我需要在这里定义一个新的向量?在 main 函数(不是我写的)中,一切都是由 new 编写的,然后我假设我也应该尽可能使用 new,但我不明白背后的逻辑。
在我的代码中,我想使用引用,因为我只想访问节点的指针而不是修改它们 - 我了解到使用对象本身需要制作副本并减慢进程,所以不是可取的 -。我的代码的哪一部分试图修改任何指针?
既然我定义了一个新向量,在返回值之前,我是否也应该删除它并使其等于 nullptr?
提前致谢!
【问题讨论】:
FWIW,不需要在析构函数中执行left = nullptr;
和 right = nullptr;
。一旦析构函数结束,这些变量就会消失,因此将它们设置为 nullptr
只会为您增加更多代码行和输入。
为什么不递归遍历树并计算节点数?
@NathanOliver 我最近开始学习 C++,我猜想在删除后将它们设置为 nullptr 是针对某种可能但不太可能的错误的安全开关。
@Daniel Langr,我之前没想到这一点,但肯定是一种更简单的方法。
【参考方案1】:
你的Node
类的问题是它不遵循the rule of 3/5/0,每当你复制Node
,并且复制很多时,你就有麻烦了,因为@一旦复制的对象超出范围,987654325@ right
节点将被删除,然后在您自己调用 delete
时再次删除。
您的count
实现中的错误摘要:
int count(Node *n)
int howManyNodes = 1;
// doesn't make sense to allocate vector dynamically
// also you forgot to delete it
std::vector<Node> *nodes = new std::vector<Node>;
// keeping nodes as value will copy them, which leeds to double free
nodes->push_back(*n);
while (!nodes->empty())
// another copy - thus another problem, but this time "only" a memory leak since you don't delete it
Node* trial = new Node(nodes->back());
if (trial->left)
std::cout << trial->left << std::endl;
// another copy
nodes->insert(nodes->begin(), *trial->left);
howManyNodes += 1;
if (trial->right)
std::cout << trial->right << std::endl;
// another copy
nodes->insert(nodes->begin(), *trial->right);
howManyNodes += 1;
nodes->pop_back();
return howManyNodes;
如何在不复制任何对象的情况下实现它:
void count(Node* n, int& numNodes)
numNodes++;
Node* left = n->left;
Node* right = n->right;
if (left) count(left, numNodes);
if (right) count(right, numNodes);
int main()
Node *n = new Node();
n->left = new Node();
n->right = new Node();
n->right->left = new Node();
n->right->right = new Node();
n->right->right->right = new Node();
int numNodes0;
count(n, numNodes);
std::cout << numNodes << std::endl;
delete n;
return 0;
这是一种递归方法,所以可能不是最好的。如果您确实需要手动实现某种树,请使用std::unique_ptr,显式删除复制构造函数/赋值,并在您的Tree
类中实现方法count
,如果您打算这样做的话。
【讨论】:
【参考方案2】:正如@pptaszni 解释的那样,在我的代码中,我正在制作实例的副本,这导致了问题。 @pptaszni 建议的递归方法更容易和更可取,这是我以前无法想到的。我还通过传递指针而不是值来纠正我的方法。这有效:
#include <iostream>
#include <vector>
class Node
public:
Node *left, *right;
Node() left = right = nullptr;
~Node()
delete left;
left = nullptr;
delete right;
right = nullptr;
;
int count(Node *n)
int howManyNodes = 1;
std::vector<Node*> nodes = ;
nodes.push_back(n);
while (!nodes.empty())
Node* trial = nodes.back();
if (trial->left)
nodes.insert(nodes.begin(), trial->left);
howManyNodes += 1;
if (trial->right)
nodes.insert(nodes.begin(), trial->right);
howManyNodes += 1;
nodes.pop_back();
// Implement count() here.
return howManyNodes;
int main()
Node *n = new Node();
n->left = new Node();
n->right = new Node();
n->right->left = new Node();
n->right->right = new Node();
n->right->right->right = new Node();
// This should print a count of six nodes
std::cout << count(n) << std::endl;
// Deleting n is sufficient to delete the entire tree
// because this will trigger the recursively-defined
// destructor of the Node class.
delete n;
n = nullptr;
return 0;
【讨论】:
以上是关于堆内存中的 C++ 向量导致分段错误的主要内容,如果未能解决你的问题,请参考以下文章