C语言中NULL赋值位置差异导致二叉树释放堆后使用报错的原因
问题分析与解决
先明确两个写法的核心差异,以及错误的根源:
第一个版本的错误原因
void freeTree (tree_t * tree) { tree_t * p = tree; while (p->left != NULL) { // <- heap-use-after-free freeTree (p->left); } while (p->right != NULL) { freeTree (p->right); } free (p); p = NULL; }
- 循环逻辑完全多余:二叉树每个节点的左/右子节点最多只有一个,释放一次就足够,用
while属于错误设计。 - heap-use-after-free触发逻辑:调用
freeTree(p->left)后,左子节点的内存已经被释放,但p->left这个指针成员的数值没有改变(依然指向已释放的堆地址)。此时while循环会再次判断p->left != NULL,接着重复调用freeTree(p->left)——这时候传入的是已释放的无效指针,在递归的freeTree函数里访问该指针的成员时,就触发了地址 sanitizer 的heap-use-after-free错误。 p = NULL毫无作用:p是函数局部变量,修改它不会影响外部传入的指针,对内存释放逻辑没有任何帮助。
第二个版本修复的原理
void freeTree (tree_t * tree) { tree_t * p = tree; while (p->left != NULL) { freeTree (p->left); p->left = NULL; // Added } while (p->right != NULL) { freeTree (p->right); p->right = NULL; // Added } free (p); // p = NULL; // Removed }
在释放左/右子树后,立刻将当前节点对应的left/right成员设为NULL,这样while循环下一次检查时会因指针为NULL终止,避免了重复传入已释放的指针进行递归,从根源上消除了非法内存访问。
不过这里的while依然多余,更简洁且正确的写法应该用if替代while(每个节点的子节点只需释放一次):
void freeTree(tree_t *tree) { if (tree == NULL) return; // 先递归释放左、右子树 freeTree(tree->left); freeTree(tree->right); // 最后释放当前节点 free(tree); }
这个写法无需手动置NULL,因为递归释放子树后,当前节点的子节点指针不会被再次访问(当前节点释放后就不再使用),逻辑更清晰,也不会出现内存访问错误。
内容的提问来源于stack exchange,提问作者lxvs
相关产品推荐
相关产品推荐

