realloc()没有赋值问题

时间:2011-01-05 13:00:12

标签: c dynamic-memory-allocation realloc

我的一位同学给我发了一个代码,询问它有什么问题。它是这样的:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int *d_array, number, divisor_count, i, size = 1;
    char answer;

    d_array = (int*) malloc(size * sizeof(int));

    do
    {
        printf("\nEnter a number:  ");
        scanf("%d", &number);
        divisor_count = 0;
        for(i = 2; i < number; i++)
            if (number % i == 0) divisor_count++;
        if(divisor_count == 0)
        {
            realloc(d_array,(size + 1) * sizeof(int));
            d_array[size - 1] = number;
            size++;
        }
        printf("\nIs there another number? y/n ");
        getchar();
        answer = getchar();
    } while (answer == 'y');

    for(i = 0; i < size - 1; i++)
        printf("\n%d", d_array[i]);

    return 0;
} 

它应该从用户那里获取数字并保留最终的数字并将其打印到最后。我的电脑上的输出类似于:

Enter a number:  3
Is there another number? y/n y
Enter a number:  5
Is there another number? y/n y
Enter a number:  8
Is there another number? y/n y
Enter a number:  7
Is there another number? y/n y
Enter a number:  2
Is there another number? y/n n
4072680
5
7
2

代码中还有其他东西,但最大的问题显然不是分配realloc()的返回值。但奇怪的是,这是我的问题,为什么这段代码显示错误的第一个素数而其他的正确?动态数组的地址可能会改变,但为什么是第二个,其余的是正确的而不是第一个?

编辑:好的,我之所以问这个是为了尝试理解这段代码中realloc()的行为,如果你有好的资源请分享。重新分配内存时(当它释放旧内存时),realloc()是否会更改旧内存位置的内容?

7 个答案:

答案 0 :(得分:4)

始终这样做:

void* new_ptr = realloc(ptr, new_size);
if(!new_ptr) error("Out of memory");
ptr = new_ptr;

原因是realloc()可能无法满足已分配的块中所请求的大小。如果需要移动到另一个内存块,它将复制先前分配的内存中的数据,释放旧的内存块,并返回新的内存块。

此外,如果realloc()返回NULL,则表示失败。在这种情况下,ptr指向的内存必须在某个时候释放,否则会出现内存泄漏。换句话说,永远不要这样做:

ptr = realloc(ptr, new_size);

答案 1 :(得分:2)

如果要调用未定义的行为,则无法在不查看操作系统和编译器内部的情况下解释结果。

我知道这不是一个非常令人满意的答案。但是,如果你能描述它背后的逻辑,它就不会被称为未定义的行为!

答案 2 :(得分:1)

问题是在realloc之后d_array指向的内存被认为是空闲的,因此代码实际上写入了空闲内存。似乎同时内存被分配给不同的东西(由scanf使用?),所以它的开头会被覆盖。当然,这是完全未定义的:释放的内存的任何部分都可能随时被覆盖。

答案 3 :(得分:1)

由于您似乎对于为什么从未定义的行为中获得可重复(即使是不正确的)输出感到好奇,这里有一个可能导致您所看到的情况。我认为理解未定义行为背后的潜在机制可能是有价值的 - 请记住,这是假设的,并且可能不是真正的原因,你看到你所看到的。对于未定义的行为,您看到的结果可能会从一个编译更改为下一个或一个运行到下一个(对于奇怪的情况,只是在程序中更改了具有未定义行为的变量名称会更改程序的输出,请参阅{ {3}})。

  1. d_array在输入malloc(size * sizeof(int)) / do循环之前调用while进行初始化。在这一点之后d_array指针永远不会改变(即使它可能不再指向已分配的内存,我们稍后会看到)

  2. 第一次存储值时,调用realloc(),但库发现它不需要更改最初给定的块并返回传递给它的值。因此3存储在此块的开头。请注意,这仍然是一个错误,因为您的程序无法知道d_array是否仍然有效,因为您忽略了realloc()的返回值。

  3. 输入5后,再次拨打realloc()。这次,库决定 必须分配不同的块。它这样做(在复制d_array指向的内容之后)。部分重新分配导致块d_array指向被释放,并且库的簿记用3覆盖此块中的4072680。也许指向图书馆关心的东西 - 谁知道。主要的是块现在再次属于库,它(不是你)可以用它做它想做的事。

  4. 现在5被写入d_array + 1(这不是一个有效的操作,因为块d_array指向已被释放)。所以d_array[0] == 4072680d_array[1] == 5

  5. 从此时起,通过d_array存储的所有值都会进入已释放的块,但无论出于何种原因,库都不会注意到正在发生的堆损坏。只是运气(如果你想找到错误,运气不好)。什么都没有被写入realloc()可能实际重新分配的块。

  6. 注意 - 就像我说的,所有这些都是对行为的一种可能的解释。实际细节可能会有所不同,实际上并不重要。一旦您访问了已释放的内存分配(无论是读取还是写入),所有下注都将关闭。未定义行为的底线规则是任何事情都可以。

答案 4 :(得分:1)

这将向您展示正在发生的事情:

#include <stdio.h>
#include <stdlib.h>

void dbg_print_array(unsigned sz, const int * array, const char * label) {
     fprintf(stderr, "{%s:\t%p:\t", label, array);
     while (sz--) {
         fprintf(stderr, " %x ", *array++);
     }
     fprintf(stderr, "}\n");
}

int main()
{
    int *d_array, number, divisor_count, i, size = 1;
    char answer;

    d_array = (int*) malloc(size * sizeof(int));
    dbg_print_array(size, d_array, "Initial");

    do
    {
        printf("\nEnter a number:  ");
        scanf("%d", &number);
        divisor_count = 0;
        for(i = 2; i < number; i++)
            if (number % i == 0) divisor_count++;
        if(divisor_count == 0)
        {
            int * p;
            dbg_print_array(d_array, size, "pre-realloc");
            p = realloc(d_array,(size + 1) * sizeof(int));
            dbg_print_array(d_array, size+1, "post-realloc (d_array)");
            dbg_print_array(p, size+1, "post-realloc (p)");
            d_array[size - 1] = number;
            size++;
        }
        printf("\nIs there another number? y/n ");
        getchar();
        answer = getchar();
    } while (answer == 'y');

    for(i = 0; i < size - 1; i++)
        printf("\n%d", d_array[i]);

    return 0;
} 

至于为什么这些数据很快就会被写入,很难说清楚。不同的堆分配实现对此可能表现得非常不同。由于重新分配是在如此小的步骤中完成的(每次数组增长1),因此将经常调用realloc。

因为即使你和我通常认为未分配的堆空间是未使用的,堆分配和释放函数也会在那里存储一些数据以跟上事情。由于每次调用realloc都会读取此数据,并且程序提供了对realloc可能假设的数据的写入由堆分配例程所拥有的,因此它可能正在读取该程序已被覆盖的内容(它每次都会写入最初分配的空间的末尾)通过循环)。在阅读完这些损坏的数据后realloc可能会根据它读取的内容做出决定,从而导致谁知道什么。在程序的这一点上,您应该将每个行为视为 undefined ,因为正常操作的基本假设不再有效。

修改

通过检查上面代码的输出,您应该能够确定realloc实际上何时返回的指针不同于传递的指针(我的猜测是为读取的最后一个整数提供空间,你的例子,因为malloc可能在第一次分配时最多为16个字节 - 也因为realloc没有abort,可能因为它从未传递过无效指针。)

realloc没有返回与传递它相同的指针时,相邻的 post-realloc 打印语句将具有不同的地址(为它们打印的第一个数字)。

答案 5 :(得分:0)

试试:

d_array = realloc(d_array,(size + 1) * sizeof(int));

这样做并且在我的电脑上工作正常。

如果您使用gcc,gcc -Wall -o会向您发出您正在寻找的警告。

你realloc()但你没有使用分配的新内存。简单地说。它仍然指向旧的(取决于realloc()使用相同的块还是将其移动到不同的位置)。如果你“幸运”并且使用相同的块你只是继续写,否则你写到旧的地方并最终写在一个你不应该写的地方(因为realloc() free()是旧块在内部。realloc()不会更改您的指针变量,只需重新分配空间。您需要更改内存地址并返回realloc()的结果。正如评论所说。您需要检查是否成功{ {1}} ation。分配的新空间包含旧缓冲区的数据(只要分配的新内存大于旧内存)。旧内存的内容不会更改,但旧位置将被释放。< / p>

答案 6 :(得分:0)

我认为你必须像这样使用realloc:

d_array = realloc(d_array,(size + 1)* sizeof(int));

而不只是:

realloc(d_array,(size + 1)* sizeof(int));

我看到的另一个问题是size = 1,所以第一次代码运行就是这样:

realloc(d_array,(size + 1)* sizeof(int));

size + 1 = 2(为2个整数分配内存,但只需要一个。)解决方案的起始大小为0。

相关问题