在如下二叉树链式转顺序存储函数中,对顺序表a扩容(char* aa = (char*)realloc(a, size*sizeof(char));)时会报:“已在 BinaryTree.exe 中执行断点指令(__debugbreak()语句或类似调用)。”
char* a = (char*)malloc(MAXSQLIST * sizeof(char));
int Aflag = 0,maxflag= MAXSQLIST;
void list_prueue(Bitree* T, char* a, int Aflag, int& maxflag) {
if (Aflag > maxflag) {
int size = maxflag + LONGSIZE;
char* aa = (char*)realloc(a, size*sizeof(char));
if (aa)
{
a = aa;
maxflag = maxflag + LONGSIZE;
}
else return;
}
if (T) {
a[Aflag] = T->data;
list_prueue(T->left, a, Aflag*2+1, maxflag);
list_prueue(T->right, a, Aflag*2+2, maxflag);
}
else { a[Aflag] = '#'; }
}
调试分析后,我发现是第二次扩容时报错。同时我观察了时期的a与aa的地址值。
我认为是“共享内存引发的悬空指针和未定义的行为问题” void list_prueue(Bitree* T, char* a, int Aflag, int& maxflag)是一个递归函数,各个递归共享顺序表a,但这里出现了一个realloc特殊情况(异地扩容)下引发的问题,在QQ日志中我介绍过realloc函数使用规则,第一次扩容时顺序表a所在的内存空间后面没有足够的连续空间扩大a指向的地址,这时空间不够,先另外找一块满足指定size的大小连续空间并分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来a所指内存区域(000001614DCAC3F0)(注意:原来指针是自动释放,不需要使用free),同时返回新分配的内存区域的首地址(000001614DCA2A10)。
也就是第一次扩容,a原来的所指向的连续内存已被释放,而本函数char* a 是传递的地址,这就意味着其他正在递归的函数中a指向了一个被释放的内存区域,此时指针a指向的就是无效内存,a变成了“悬空指针”, 这时候如果再次访问这个指针,就会导致不可预测的结果,可能会引发程序崩溃或者数据损坏等问题。
第二次扩容时,realloc再次访问这个指针(000001614DCAC3F0),它就傻眼了(?-?)当a为悬空指针时,realloc函数的行为是未定义的。这意味着它可能会导致程序崩溃或产生其他不可预测的结果。因此,在使用realloc函数时,应该始终确保a指向已分配的内存块。
于是我把char* a改成char* &a;指针引用,这就可以了!
注:未定义行为(undefined behavior)”是在C语言标准中定义的。
标准原文为:behavior, upon use of a nonportable or erroneous program construct or of erroneous data,for which this International Standard imposes no requirements
译文为:在使用不可移植的或错误的程序结构或错误的数据时的行为,本国际标准对此没有要求
简而言之,未定义行为就是指程序执行时的行为是无法预知的。
例如,缓冲区访问越界就是一种未定义行为。当发生该行为时,我们无法确定越界访问内存的真实地址和内容,因此也就无法预知该行为会对程序执行产生的影响
关于realloc()悬空指针和未定义的行为可详见关于C#:realloc()悬空指针和未定义的行为 | 码农家园