【问题标题】:realloc() without assignment problemrealloc() 没有赋值问题
【发布时间】:2011-06-04 00:08:33
【问题描述】:

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

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

【问题讨论】:

  • 调用未定义的行为——正如你的同事无疑正在做的那样——会导致一切成为可能。一旦你调用未定义的行为,你所看到的就是完全有效的;进一步讨论它真的没有多大意义。 GIGO - 垃圾进,垃圾出。你很不幸,任何事情似乎都有效(是的,我的意思是不幸运!)。

标签: c dynamic-memory-allocation realloc


【解决方案1】:

总是这样做:

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);

【讨论】:

  • 感谢您的回答,但我不是在要求正确的代码,我只是在问为什么它在上面编写的代码中输出。
  • 可能是因为内存管理例程用一些幻数标记了代码以帮助调试。启用或不启用调试有什么区别吗?使用 Visual C++,您可以根据调试模式设置获得完全不同的内存管理例程行为。
【解决方案2】:

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

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

【讨论】:

  • 这实际上是堆分配库的内部结构,它可能是操作系统的一部分(不太可能在台式计算机上),但可能是编译器附带的库的一部分(尽管不一定) .
【解决方案3】:

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

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

【讨论】:

  • 有趣的是,当在 Ubuntu 上使用 GCC 编译代码时,它看起来好像运行良好。如果我首先在 Ubuntu 中编译此代码,我将不会学习 realloc() 的正确用法。正如我们在课堂上没有分配使用它一样。感谢所有的帮助,我真的很感激。
【解决方案4】:

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

【讨论】:

  • 好吧,我看到这是未定义的行为,但输出始终相同。我只是想知道 realloc() 在将内存移动到另一个地方时是否会改变内存的内容。
  • @evothur:是的,它改变了内容(如果新尺寸大于旧尺寸)。见:cplusplus.com/reference/clibrary/cstdlib/realloc 顺便说一下,这个参考可能是你朋友代码的原型。 :-)
  • @evothur:引用之前的参考:“如果新的大小更大,新分配部分的值是不确定的。”
【解决方案5】:

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

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

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

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

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

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

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

【讨论】:

  • 感谢您的所有解释。它真的帮了我很多,我真的很感激。
【解决方案6】:

试试看:

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

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

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

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

【讨论】:

  • 而当 realloc 返回 NULL 时,你在使用时会被彻底搞砸。
  • 这包含在我的问题中,我知道这是正确的方法,但我不想更正代码。问题是,为什么上面的错误代码会给出这样的输出,它背后的逻辑是什么?
  • 好吧,我检查了它,通常 realloc() 返回一个指向内存的另一个位置的指针,但输出总是相同的,第一个不正确,其余的都是。
【解决方案7】:

我认为你必须像这样使用 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 开始大小。

【讨论】:

  • 感谢您的回答,但我不是要更正代码,我只是想了解 realloc() 的行为
猜你喜欢
  • 2011-04-22
  • 1970-01-01
  • 2023-04-06
  • 2015-02-25
  • 1970-01-01
  • 2010-11-07
  • 1970-01-01
  • 2011-09-23
  • 2011-03-04
相关资源
最近更新 更多