简体   繁体   English

了解铿锵有力的“DeadStores”和“NewDeleteLeaks”警告

[英]Understanding clang-tidy "DeadStores" and "NewDeleteLeaks" warnings

I am relatively new to c++ programming, especially when dealing with OOP and pointers.我对 c++ 编程比较陌生,尤其是在处理 OOP 和指针时。

I am trying to implement a binary search tree, and here is the code I have我正在尝试实现二叉搜索树,这是我拥有的代码

struct TreeNode {
  int val;
  TreeNode* left;
  TreeNode* right;

  TreeNode() : val(0), left(NULL), right(NULL){};
  TreeNode(int v) : val(v), left(NULL), right(NULL){};
  TreeNode(int v, TreeNode* l, TreeNode* r) : val(v), left(l), right(r){};
};

void insert(TreeNode* root, TreeNode* v) {
  // inserts v into root, assuming values distinct
  if (root == NULL) {
    root = v; // L52
    return;
  }
  if (v == NULL) {
    return;
  }

  // traverse through tree using structure of BST
  TreeNode* cur = root;
  while (cur != NULL) {
    if (cur->val <= v->val) {
      if (cur->right == NULL) {
        cur->right = new TreeNode(v->val);
        break;
      }
      cur = cur->right;
    } else if (cur->val >= v->val) {
      if (cur->left == NULL) {
        cur->left = new TreeNode(v->val);
        break;
      }
      cur = cur->left;
    }
  }
}

void insert(TreeNode* root, int v) {
  insert(root, new TreeNode(v)); // L78
}

Now, I have two questions.现在,我有两个问题。

  1. I realised that the L52 labelled above would not work.我意识到上面标记的 L52 不起作用。 For example, if I do TreeNode* node = new TreeNode(5); TreeNode* empty = NULL; insert(empty, node);例如,如果我执行TreeNode* node = new TreeNode(5); TreeNode* empty = NULL; insert(empty, node); TreeNode* node = new TreeNode(5); TreeNode* empty = NULL; insert(empty, node); , it would not work. ,它不会工作。 I am not sure how to fix this, so any help would be great.我不知道如何解决这个问题,所以任何帮助都会很棒。

  2. Running clang-tidy on my code, I get the following slightly confusing warning:在我的代码上运行 clang-tidy,我得到以下稍微令人困惑的警告:

./main.cpp:78:69: warning: Potential memory leak [clang-analyzer-cplusplus.NewDeleteLeaks]
void insert(TreeNode* root, int v) { insert(root, new TreeNode(v)); }
                                                                    ^
./main.cpp:132:3: note: Calling 'insert'
  insert(node, 3);
  ^
./main.cpp:78:51: note: Memory is allocated
void insert(TreeNode* root, int v) { insert(root, new TreeNode(v)); }
                                                  ^
./main.cpp:78:69: note: Potential memory leak
void insert(TreeNode* root, int v) { insert(root, new TreeNode(v)); }
                                                                    ^

I am not sure what this means, and whether it would be dangerous in any way.我不确定这意味着什么,以及它是否会以任何方式危险。 I found this from the LLVM docs and it seems that the temporary new TreeNode(v) is causing the problem.我从 LLVM 文档中找到了这个,似乎临时的new TreeNode(v)导致了这个问题。 What is a better way to achieve what I want?有什么更好的方法来实现我想要的? Any further explanation or resources that I can read would be great.我可以阅读的任何进一步的解释或资源都会很棒。

Of course, any improvement to my bad coding style would be great as well ;) formatter saves the day.当然,对我糟糕的编码风格的任何改进都会很棒;)格式化程序可以节省一天的时间。

Thank you!谢谢!

Q1: How do I insert into a binary tree? Q1:如何插入二叉树?

The main problem with the fragment:片段的主要问题:

void insert(TreeNode* root, TreeNode* v) {
  // inserts v into root, assuming values distinct
  if (root == NULL) {
    root = v; // L52
    return;
  }

is, although you have set a new value for root , root is a parameter to the insert function, and consequently is discarded when insert returns.也就是说,尽管您为root设置了新值,但rootinsert函数的参数,因此在insert返回时被丢弃。

One way to fix this is to return the new value:解决此问题的一种方法是返回新值:

TreeNode*             // <-- changed return type
    insert(TreeNode* root, TreeNode* v) {
  // inserts v into root, assuming values distinct
  if (root == NULL) {
    root = v; // L52
    return root;      // <---- changed to return a value
  }

Every place insert returns (including at the end), return the value of root .每个地方insert返回(包括在末尾),返回root的值。

Then when you call insert , save the returned value:然后当你调用insert时,保存返回值:

TreeNode* root = ...;       // some existing tree
root = insert(root, 5);     // insert 5 and save the result

Now, the new value of root points to the tree that contains the inserted value.现在, root的新值指向包含插入值的树。

Q2: What is clang-tidy complaining about? Q2:clang-tidy 在抱怨什么?

Invoking new allocates memory.调用new分配内存。 It must eventually be freed, otherwise (if this keeps happening) your program will eventually use all available memory.它最终必须被释放,否则(如果这种情况继续发生)您的程序最终将使用所有可用内存。 See the question When should I use the new keyword in C++?请参阅问题何时应该在 C++ 中使用 new 关键字? . . Note especially this advice: "every time you type new , type delete ."请特别注意以下建议:“每次键入new时,键入delete 。”

Now, in this code as it stands, the problem is actually not the missing delete , it is the failure to return the updated root .现在,在这段代码中,问题实际上不是缺少delete ,而是未能返回更新后的root clang-tidy realizes that, because the updated root is never returned, you can't possibly eventually delete it, so complains. clang-tidy意识到,因为更新的root永远不会返回,你不可能最终delete它,所以抱怨。 Once you start returning that value, clang-tidy will wait to see what you do next.一旦你开始返回那个值, clang-tidy就会等着看你下一步做什么。

As for what to do next, one simple approach is to regard TreeNode as owning its children, meaning it is obliged to delete them when it is destroyed.至于接下来要做什么,一种简单的方法是将TreeNode视为拥有其子节点,这意味着它必须在销毁时将其delete For example, first add a destructor method to TreeNode :例如,首先给TreeNode添加一个析构方法:

struct TreeNode {
  ...
  ~TreeNode() {        // <--- this is the destructor
    delete left;       // free left child (if not NULL)
    delete right;      // free right child
  }
};

Then in your code that manipulates trees, remember to delete the root node when finished:然后在操作树的代码中,记得在完成后delete根节点:

TreeNode* root = NULL;
root = insert(root, 1);
root = insert(root, 2);
delete root;            // <--- free entire tree

A note about smart pointers关于智能指针的说明

My suggestions above use a very direct, literal style of memory management, with explicit use of new and delete where needed.我上面的建议使用了一种非常直接的、字面意义上的内存管理方式,在需要的地方显式地使用了newdelete However, it is easy to forget to use delete , and tricky to get it right in the presence of exceptions.但是,很容易忘记使用delete ,并且在出现异常时很难正确使用它。 A commonly preferred alternative is to use smart pointers .通常首选的替代方法是使用智能指针 But that's a somewhat advanced topic, so when just starting out, I recommend first getting comfortable with explicit new and delete , but keep in mind there is a better way when you're ready for it.但这是一个有点高级的话题,所以刚开始时,我建议先熟悉显式newdelete ,但请记住,当你准备好时,还有更好的方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM