问题描述
我编写了一个小程序来遍历二叉树,我想练习realloc
,并编写了以下代码以用二叉搜索树的元素动态填充数组:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
};
void inorder(struct TreeNode *root,int *inorder_arr,int *size) {
if (root) {
inorder(root->left,inorder_arr,size);
inorder_arr[(*size)-1] = root->val;
(*size)++;
inorder_arr = (int *)realloc(inorder_arr,(*size) * sizeof(int));
inorder(root->right,size);
}
}
bool findTarget(struct TreeNode *root,int k) {
if (!root) return false;
int size =1;
int *inorder_arr = (int *)malloc(sizeof(int));
inorder(root,&size);
return false;
}
int main () {
// this is for creating a binary search tree which its inorder traversal looks like this : 2,3,4,5,6,7
struct TreeNode *root = (struct TreeNode *)malloc(sizeof(struct TreeNode));
root->val = 5;
root->left = (struct TreeNode *)calloc(1,sizeof(struct TreeNode));
root->left->val = 3;
root->right = (struct TreeNode *)calloc(1,sizeof(struct TreeNode));
root->right->val = 6;
root->right->right = (struct TreeNode *)calloc(1,sizeof(struct TreeNode));
root->right->right->val = 7;
root->left->right = (struct TreeNode *)calloc(1,sizeof(struct TreeNode));
root->left->right->val = 4;
root->left->left = (struct TreeNode *)calloc(1,sizeof(struct TreeNode));
root->left->left->val = 2;
printf("%d",FindTarget(root,9));
}
〜请忽略一个事实,即我的函数FindTarget
将始终返回false
,这不是该程序的全部目的,也不是我的问题的目的。
也可能需要注意,我的二进制搜索树如下所示: binary search tree.png
〜我的程序似乎可以运行,但是在使用地址清理器进行编译时遇到了一个大问题:
gcc -ggdb 2_sum_bst.c -fsanitize=address
当我运行程序“ a.out”时,出现以下错误:
=================================================================
==13399==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000014 at pc 0x55c7a8ef5d77 bp 0x7ffebafbf0c0 sp 0x7ffebafbf0b0
WRITE of size 4 at 0x602000000014 thread T0
#0 0x55c7a8ef5d76 in inorder /home/yarin/my_dev/C_C++_learning/basics/2_sum_bst.c:15
#1 0x55c7a8ef5cb8 in inorder /home/yarin/my_dev/C_C++_learning/basics/2_sum_bst.c:14
#2 0x55c7a8ef5ef3 in findTarget /home/yarin/my_dev/C_C++_learning/basics/2_sum_bst.c:28
#3 0x55c7a8ef628b in main /home/yarin/my_dev/C_C++_learning/basics/2_sum_bst.c:45
#4 0x7f1186741b96 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)
#5 0x55c7a8ef5b79 in _start (/home/yarin/my_dev/C_C++_learning/basics/a.out+0xb79)
0x602000000014 is located 0 bytes to the right of 4-byte region [0x602000000010,0x602000000014)
freed by thread T0 here:
#0 0x7f1186beff30 in realloc (/usr/lib/x86_64-linux-gnu/libasan.so.4+0xdef30)
#1 0x55c7a8ef5da6 in inorder /home/yarin/my_dev/C_C++_learning/basics/2_sum_bst.c:17
#2 0x55c7a8ef5cb8 in inorder /home/yarin/my_dev/C_C++_learning/basics/2_sum_bst.c:14
#3 0x55c7a8ef5cb8 in inorder /home/yarin/my_dev/C_C++_learning/basics/2_sum_bst.c:14
#4 0x55c7a8ef5ef3 in findTarget /home/yarin/my_dev/C_C++_learning/basics/2_sum_bst.c:28
#5 0x55c7a8ef628b in main /home/yarin/my_dev/C_C++_learning/basics/2_sum_bst.c:45
#6 0x7f1186741b96 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)
prevIoUsly allocated by thread T0 here:
#0 0x7f1186befb40 in __interceptor_malloc (/usr/lib/x86_64-linux-gnu/libasan.so.4+0xdeb40)
#1 0x55c7a8ef5ecf in findTarget /home/yarin/my_dev/C_C++_learning/basics/2_sum_bst.c:27
#2 0x55c7a8ef628b in main /home/yarin/my_dev/C_C++_learning/basics/2_sum_bst.c:45
#3 0x7f1186741b96 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)
SUMMARY: AddressSanitizer: heap-use-after-free /home/yarin/my_dev/C_C++_learning/basics/2_sum_bst.c:15 in inorder
Shadow bytes around the buggy address:
0x0c047fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c047fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c047fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c047fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c047fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c047fff8000: fa fa[fd]fa fa fa 00 fa fa fa fa fa fa fa fa fa
0x0c047fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==13399==ABORTING
〜如我们所见,我得到了“堆后使用”,我很好奇并开始调试程序
〜我开始调试程序并意识到我的错误,当我的函数“ inorder”正在执行其递归调用时,它开始回溯指向调用堆栈中每次调用的数组的指针是特定于该特定对象的call,但是我使用了realloc和realloc释放了内存并分配了新的内存,并返回了该调用中的该指针的指针,而不是返回到调用堆栈中的所有指针,因此运行此问题并不复杂,我认为我该如何解决这个问题没有使用全局指针,我考虑过要使用静态指针,但这也行不通。
到目前为止,我不认为不使用全局指针如何解决该问题,因为我是C编程的初学者,并且想知道最佳的解决方案,所以我想听听一些建议。在这种情况下,无需使用全局指针或遍历整个树即可获得其大小,然后使用malloc为所有节点获取足够的内存,如您所见,我的目的是在程序遍历该树时做到这一点。
在此先感谢您的阅读和帮助!
解决方法
问题注释中明确指出,主要问题
是由于在发布代码的inorder()
函数中,
原始计数器(size
中的findTarget()
)实际上已更新,但
原始指针指向动态分配存储的开头
(inorder_arr
中的findTarget()
)不是。
以下代码旨在更简单地重现相同的问题 上下文(只需附加一个整数,无需任何递归)。
extend_dyn_array_bad()
功能类似于inorder()
功能,因为它的参数使其能够更改
计数器,但不指向动态开始的指针
分配的存储空间。
另一方面,extend_dyn_array()
函数接收
指针参数按地址的方式与计数器相同
参数:
- 一个
int
,以便通过地址传递给函数 可能会改变它,需要一个int *
参数, - 一个
int *
,以便通过地址传递给函数 可能会改变它,需要一个int **
参数。 (我们只需在类型中添加*
即可,就像它在其原始上下文中一样)。
在单个变量上进行多级间接处理
并不是很容易阅读,这就是为什么我建议摆脱
进入时尽快进行这种额外的间接访问
功能,然后在这些本地计算机上执行所有实际工作
变量,完成后更改原始参数
感谢这种额外的间接级别(请参阅三篇评论
extend_dyn_array()
函数中的步骤。
还请注意,问题的findTarget()
功能依赖于
永不释放(free()
)的数组的动态分配。
/**
gcc -std=c99 -o prog_c prog_c.c \
-pedantic -Wall -Wextra -Wconversion \
-Wwrite-strings -Wold-style-definition -Wvla \
-g -O0 -UNDEBUG -fsanitize=address,undefined
**/
#include <stdio.h>
#include <stdlib.h>
void
extend_dyn_array_bad(int *values,int *count)
{
++(*count);
values=realloc(values,sizeof(*values)*(size_t)(*count));
if(values==NULL)
{
abort();
}
// after realloc,values may point to a different address
// which is known and usable by this function but that
// is not know by the v variable in main().
values[*count-1]=*count; // initialise last element
}
void
extend_dyn_array(int **inout_values,int *inout_count)
{
//-- load inout-parameters --
int *values=*inout_values;
int count=*inout_count;
//-- perform actual work --
++count;
values=realloc(values,sizeof(*values)*(size_t)count);
if(values==NULL)
{
abort();
}
values[count-1]=count; // initialise last element
//-- store out-parameters --
*inout_values=values;
*inout_count=count;
}
int
main(void)
{
int *v=NULL;
int n=0;
for(int i=0; i<10000; ++i)
{
extend_dyn_array(&v,&n); // both v and n may be altered
// extend_dyn_array_bad(v,&n); // only n may be altered
}
for(int i=0; i<5; ++i) { printf("%d ",v[i]); } printf("... ");
for(int i=n-5; i<n; ++i) { printf("%d ",v[i]); } printf("\n");
free(v);
return 0;
}