【问题标题】:Calloc/Malloc and freeing to often or too large a space?Calloc/Malloc 并经常释放或释放太大的空间?
【发布时间】:2017-03-05 23:51:58
【问题描述】:

免责声明,这是对学校作业的帮助。话虽这么说,我的问题只发生在大约 50% 的时间里。这意味着如果我编译并运行我的代码而不进行编辑,有时它会一直运行到最后,有时它不会。通过使用多个打印语句,我确切地知道问题发生在哪里。问题出现在我对 hugeDestroyer 的第二次调用中(就在打印 354913546879519843519843548943513179 部分之后),更确切地说是在免费(p->digits)部分。

我已经尝试了此处的建议 (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;
}

【问题讨论】:

  • 你试过使用调试器吗?
  • for(; i &gt;= 0; i--) 循环比您分配的空间多写一位(例如,如果 p-&gt;digits == 1 则您为 1 个 int 分配空间,但此循环运行两次,i == 1 然后 i == 0第二次迭代写入p-&gt;digits[1],超出范围)
  • 我已经使用了带有 codeBlocks 的调试器并产生了一些效果,这就是导致我进入 free(p->digits) 部分的原因,但它只是有一个内存地址作为指向该行的注释并且是除了向我展示这一点之外,并没有什么帮助。我确实将您指出的 sn-p 更改为 for(; i - 1>= 0; i--) 现在让我在段错误之前降到 INT_MAX 部分,所以这绝对是进步。似乎我的问题可能与免费无关,但超出了界限。
  • 你只需要释放p,如果你已经完成p那么就不需要释放p->digits
  • 如果你在linux上你可以使用valgrind来调试这类问题

标签: c pointers struct free


【解决方案1】:

首先,非常突出的问题。您对主题做了很多研究,总的来说,您自己解决了这个问题,我在这里主要是为了确认您的发现。

free(p->digits) 在语法上是否正确,如果正确,为什么我在运行代码时会遇到一半的问题?

语法正确。 @Shihab 在 cmets 中建议不要释放 p-&gt;digits 和仅释放 p,但这样的建议是错误的,它会导致内存泄漏。有一个简单的规则:对于每个 calloc,您最终都必须调用 free,因此您当前释放 p-&gt;digits 然后 p 的方法完全没问题。

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

正如您已经发现的那样,在大多数实现中,诸如calloc 之类的内存例程会导致分配带有前置元信息的缓冲区。您收到指向缓冲区本身的指针,但在此指针之前的一小段信息对于进一步管理缓冲区(例如释放)至关重要。将 11 个整数写入用于 10 的缓冲区,您可能会损坏缓冲区后面的块的元信息。损坏是否真的发生以及它的后果是什么,在很大程度上取决于实现细节和当前内存对齐(缓冲区后面的块,究竟是什么元数据被损坏)。每两次执行都会导致一次崩溃,这并不让我感到惊讶,在我的系统上观察到 100% 的崩溃重现也不让我感到惊讶。

其次,如何在我的函数中防范这种行为?

让我们从修复溢出开始。有几个:

  • parseString:循环for(; i &gt;= 0; i--) 被执行length+1 次,所以p-&gt;digits 被溢出
  • parseInt:循环for (i = 0; i &lt;= p-&gt;length; i++, j++)被执行length+1次,所以p-&gt;digits被溢出

直接访问 C++ 中的内存管理容易出错且调试麻烦。内存泄漏和缓冲区溢出是程序员生活中最糟糕的噩梦,通常最好简化/减少对动态内存的直接使用,当然,除非您正在学习应对它。如果您需要坚持大量直接内存管理,请查看valgrind,它旨在检测所有此类事情。

顺便说一句,您的程序中也存在内存泄漏:每次调用parseInt 都会为a 分配缓冲区,但从不释放它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-08-29
    • 2012-02-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-08
    • 1970-01-01
    • 2021-04-28
    相关资源
    最近更新 更多