堆内存中的 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++ 向量导致分段错误的主要内容,如果未能解决你的问题,请参考以下文章

如何修复 C++ 中的“分段错误”错误

C++分段错误中的堆算法

C++ STL 内存管理:堆栈还是堆?

C ++从向量中删除指针会导致堆错误

c++ 类向量中的段错误

内存堆问题 C++,动态分配多维数组