1

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

#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() 是否会更改旧内存位置的内容?

4

7 回答 7

5

总是这样做:

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);
于 2011-01-05T13:11:56.070 回答
2

如果您正在调用未定义的行为,那么如果不查看操作系统和编译器内部,就无法解释结果。

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

于 2011-01-05T13:28:38.297 回答
1

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

于 2011-01-05T13:10:53.997 回答
1

由于您似乎很想知道为什么您会从未定义的行为中获得可重复(即使不正确)的输出,所以这里有一种可能会导致您所看到的情况。我认为了解未定义行为背后的潜在机制可能很有价值——请记住,这是假设的,可能并不是你看到你所看到的东西的真正原因。对于未定义的行为,您看到的结果可能会从一次编译更改为下一次编译或一次运行更改为下一次(对于一种奇怪的情况,即仅更改具有未定义行为的程序中的变量名称会更改程序的输出,请参阅Unexpected带有 MSVC 与 TCC 的 Bubblesort 程序的输出)。

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

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

  3. 输入时5,将进行另一个调用realloc()。这一次,库决定它必须分配一个不同的块。它会这样做(在复制d_array指向的内容之后)。这种重新分配的部分结果是块d_array指向被释放,图书馆的簿记34072680. 也许是指向图书馆关心的东西的指针——谁知道呢。主要的是该块现在再次属于库,它(不是你)可以用它做它想做的事。

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

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

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

于 2011-01-05T18:57:52.873 回答
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,可能会根据它读取的内容做出决定,从而导致谁知道什么。在程序的这一点上,您应该将每个行为视为未定义,因为为正常操作所做的基本假设不再有效。

编辑

通过检查上述代码的输出,您应该能够确定何时realloc实际返回与传递的指针不同的指针(我的猜测是在您的示例中为读入的最后一个整数腾出空间,因为 malloc 可能四舍五入第一次分配到 16 个字节 - 也是因为realloc没有abort,可能是因为它从未传递过无效指针)。

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

于 2011-01-05T23:13:31.790 回答
0

试试看:

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

这样做并在我的计算机上运行良好。

如果你使用 gcc agcc -Wall -o会给你你正在寻找的警告。

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

于 2011-01-05T13:08:09.143 回答
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 开始大小。

于 2011-01-05T13:38:27.827 回答