2017-03-05 54 views
2

免责声明,这是一个学校作业的帮助。这就是说,我的问题只发生在大约50%的时间。这意味着如果我编译并运行我的代码而不进行编辑,有时它会将其贯穿到最后,而其他时间则不会。通过使用多个打印语句,我确切地知道问题发生的位置。这个问题发生在我第二次打电话给hugeDestroyer(正好在打印354913546879519843519843548943513179部分之后),更确切地说是在免费(p->数字)部分。Calloc/Malloc并释放空间太大或太大?

我试过了这里找到的建议(free a pointer to dynamic array in c),并将指针设置为NULL后释放它们没有运气。

通过一些挖掘和灵魂搜索,我已经了解了一些关于(How do malloc() and free() work?)的免费作品,我想知道我的问题是来自用户Juergen在他的回答中提到的,并且我正在“覆盖”管理数据免费清单。

要清楚,我的问题是双重的。

是免费的(p->数字)在语法上是正确的,如果是这样的话,为什么我在运行代码时会遇到一半的麻烦?其次,我如何防范这种行为在我的职能?

#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; 
} 
+1

您是否尝试过使用调试器? –

+1

'for(; i> = 0; i - )'循环比你分配的空间多写入一个数字(例如,如果'p-> digits == 1'那么你为1个int分配空间,但是这个循环运行两次,'i == 1',然后'i == 0',第二次迭代写入'p-> digits [1]',这是超出范围的) –

+0

我已经使用了带有某些效果的codeBlocks的调试器,是什么让我到免费(p->数字)部分,但它只是有一个内存地址作为注释指向该行,并没有真正有用的除了显示我。 我确实改变了你指出的片段(; i - 1> = 0; i--),现在我可以在segfaulting之前将它放到INT_MAX部分,所以这绝对是进步。似乎我的问题可能不是免费的,但超越了界限。 –

回答

2

首先,真的很突出的问题。你对话题做了大量的研究,一般来说,你自己解决了这个问题,我在这里主要是为了证实你的发现。

是否免费(p-> digits)语法上正确,如果是这样,为什么我在运行代码时会遇到一半的麻烦?

语法正确。 @Shihab在评论中建议只发布p->digits和版本p,但这样的建议是错误的,它会导致内存泄漏。有一条简单的规则:对于每个calloc,您最终必须免费拨打电话,因此您目前的解决方案是释放p->digits,然后p完全正常。

但是,程序在有效行上失败。这怎么可能?快速回答:由于负责跟踪已分配/空闲块列表的元信息的损坏,免费无法完成其工作。在某些程序上,程序损坏了元信息,但这只是在试图使用它时才显示出来。

正如您已经发现的那样,在大多数实现中,内存例程(如calloc)会导致带有前置元信息的缓冲区分配。您会收到指向缓冲区的指针,但在该指针对进一步缓冲区管理(例如释放)至关重要之前,还会收到一小段信息。将11个整数写入预期为10的缓冲区中,可能会破坏缓冲区后面块的元信息。腐败究竟是否真的发生了,其后果是什么,严重依赖于实现细节和当前的内存对齐(缓冲区后面是什么块,究竟是什么元数据被破坏)。我并不感到惊讶,你会看到每两次执行一次崩溃,在我的系统上观察到100%的崩溃再现,这也不意外。

其次,我该如何防范这种行为在我的职能?

让我们从修复溢出开始。有他们夫妇:

  • 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分配缓冲区,但永远不会释放它。