免责声明,这是对学校作业的帮助。话虽这么说,我的问题仅在大约 50% 的情况下发生。这意味着如果我在不进行编辑的情况下编译并运行我的代码,有时它会一直运行到最后,有时则不会。通过使用多个打印语句,我可以准确地知道问题发生在哪里。这个问题发生在我第二次调用巨大的Destroyer时(就在打印354913546879519843519843548943513179部分之后),更准确地说是在空闲(p->数字)部分。
我尝试了此处找到的建议( free a pointer to dynamic array in c ),并在释放指针后将它们设置为 NULL,但没有成功。
通过一些挖掘和灵魂搜索,我从( How do malloc() and free() work? )了解了更多关于免费工作原理的信息,我想知道我的问题是否源于用户 Juergen 在他的回答中提到的内容,以及我正在“覆盖”管理数据空闲列表。
需要明确的是,我的问题有两个。
free(p->digits) 在语法上是否正确?如果是,为什么我在运行代码时可能会遇到一半的问题?
其次,我如何在我的函数中防范这种行为?
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
typedef struct HugeInteger
{
// a dynamically allocated array to hold the digits of a huge integer
int *digits;
// the number of digits in the huge integer (approx. equal to array length)
int length;
} HugeInteger;
// Functional Prototypes
int str2int(char str) //converts single digit numbers contained in strings to their int value
{
return str - 48;
}
HugeInteger *parseInt(unsigned int n)
{
int i = 0, j = 0;
int *a = (int *)calloc(10, sizeof(int));
HugeInteger *p = (HugeInteger *)calloc(1, sizeof(HugeInteger));
if(n == 0)
{
p->digits = (int *)calloc(1, sizeof(int));
p->length = 1;
return p;
}
while(n != 0)
{
a[i] = n % 10;
n = n / 10;
i++;
}
p->length = i;
p->digits = (int *)calloc(p->length, sizeof(int));
for(i = 0; i <= p->length; i++, j++)
p->digits[j] = a[i];
return p;
}
HugeInteger *parseString(char *str) //notice datatype is char (as in char array), so a simple for loop should convert to huge int array
{
int i = 0, j = 0;
HugeInteger *p = (HugeInteger *)calloc(1, sizeof(HugeInteger));
if(str == NULL)
{
free(p);
p = NULL;
return p;
}
else
{
for(i=0; str[i] != '\0'; i++)
;
p->length = i;
p->digits = (int *)calloc(p->length, sizeof(int));
for(; i >= 0; i--)
p->digits[j++] = str2int(str[i - 1]);
}
return p;
} //end of HugeInteger *parseString(char *str)
HugeInteger *hugeDestroyer(HugeInteger *p)
{
//printf("No problem as we enter the function\n");
if(p == NULL)
return p;
//printf("No problem after checking for p = NULL\n");
if(p->digits == NULL)
{
free(p);
p = NULL;
return p;
}
//printf("No Problem after checking if p->digits = NULL\n");
//else
//{
free(p->digits);
printf("We made it through free(p->digits)\n");
p->digits = NULL;
printf("We made it through p->digits = NULL\n");
free(p);
printf("We made it through free(p)\n");
p = NULL;
printf("We made it through p = NULL\n");
return p;
//}
//return NULL;
}//end of HugeInteger *hugeDestroyer(HugeInteger *p)
// print a HugeInteger (followed by a newline character)
void hugePrint(HugeInteger *p)
{
int i;
if (p == NULL || p->digits == NULL)
{
printf("(null pointer)\n");
return;
}
for (i = p->length - 1; i >= 0; i--)
printf("%d", p->digits[i]);
printf("\n");
}
int main(void)
{
HugeInteger *p;
hugePrint(p = parseString("12345"));
hugeDestroyer(p);
hugePrint(p = parseString("354913546879519843519843548943513179"));
hugeDestroyer(p);
hugePrint(p = parseString(NULL));
hugeDestroyer(p);
hugePrint(p = parseInt(246810));
hugeDestroyer(p);
hugePrint(p = parseInt(0));
hugeDestroyer(p);
hugePrint(p = parseInt(INT_MAX));
hugeDestroyer(p);
//hugePrint(p = parseInt(UINT_MAX));
//hugeDestroyer(p);
return 0;
}
最佳答案
首先,这是一个非常突出的问题。您对这个主题做了很多研究,一般来说,自己解决了这个问题,我来这里主要是为了证实您的发现。
Is free(p->digits) syntactically correct and if so why might I have trouble half the time when running the code?
语法正确。 @Shihab 在评论中建议不要发布 p->digits
并发布p
只是,但这样的建议是错误的,它会导致内存泄漏。有一个简单的规则:对于每个 calloc,您最终必须调用 free,因此您当前的释放方法 p->digits
然后p
完全没问题。
但是,程序在有效行上失败。这怎么可能?快速回答:由于负责跟踪分配/空闲 block 列表的元信息损坏,free 无法完成其工作。在某些时候,程序损坏了元信息,但这只有在尝试使用它时才会被揭露。
正如您已经发现的,在大多数实现中,内存例程如 calloc
结果分配带有前置元信息的缓冲区。您收到指向缓冲区本身的指针,但该指针之前的一小段信息对于进一步的缓冲区管理(例如释放)至关重要。将 11 个整数写入原本用于 10 个的缓冲区中,您可能会损坏缓冲区后面的 block 的元信息。损坏是否真的发生以及其后果是什么,在很大程度上取决于实现细节和当前内存对齐(缓冲区后面的 block ,到底是什么元数据被损坏)。对于每两次执行会出现一次崩溃,我并不感到惊讶,对于在我的系统上观察到 100% 的崩溃再现,我也不感到惊讶。
Secondly, how can I guard against this kind of behavior in my functions?
让我们从修复溢出开始。其中有几个:
-
parseString
:循环for(; i >= 0; i--)
被执行length+1
次,所以p->digits
已溢出 -
parseInt
:循环for (i = 0; i <= p->length; i++, j++)
被执行length+1
次,所以p->digits
已溢出
直接访问 C++ 中的内存管理很容易出错并且调试起来很麻烦。内存泄漏和缓冲区溢出是程序员生活中最糟糕的噩梦,通常最好简化/减少动态内存的直接使用,当然,除非您正在学习应对它。如果您需要坚持大量直接内存管理,请查看 valgrind ,它的目的是检测所有此类事情。
顺便说一下,你的程序中还存在内存泄漏:每次调用parseInt
为 a
分配缓冲区,但从不释放它。
关于Calloc/Malloc 并经常释放或释放太大的空间?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42615496/