linux和osx中不同的realloc行为

时间:2012-03-05 01:32:09

标签: c linux macos realloc errno

我有这个简单的C程序:

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

int main (int argc, char **argv) {
    int i = 0;
    int j = 0;
    size_t size = 4194304; /* 4 MiB */
    char *buffer = malloc(size);
    char *buffers[10] = {NULL};
    void *tmp_pointer = NULL;
    fprintf(stderr, "initial size == %zu\n", size);
    fprintf(stderr, "initial buffer == %p\n\n", buffer);
    srand(time(NULL));
    /* let's see when it fails ... */
    for (; i < 10; ++i) {
        /* some random writes */
        for (j = 0; j < 1000; ++j) {
            buffer[rand() % size] = (char)(rand());
        }
        /* some interleaving memory allocations */
        buffers[i] = malloc(1048576); /* 1 MiB */
        size *= 2;
        fprintf(stderr, "new size == %zu\n", size);
        tmp_pointer = realloc(buffer, size);
        if ((tmp_pointer == NULL) || (errno != 0)) {
            fprintf(stderr, "tmp_pointer == %p\n", tmp_pointer);
            fprintf(stderr, "errno == %d\n", errno);
            perror("realloc");
            return (1);
        } else {
            buffer = tmp_pointer;
        }
        fprintf(stderr, "new buffer == %p\n\n", buffer);
    }
    fprintf(stderr, "Trying to free the buffers.\n");
    free(buffer);
    if (errno != 0) {
        fprintf(stderr, "errno == %d\n", errno);
        perror("free(buffer)");
        return (2);
    }
    for (i = 0; i < 10; ++i) {
        free(buffers[i]);
        if (errno != 0) {
            fprintf(stderr, "i == %d\n", i);
            fprintf(stderr, "errno == %d\n", errno);
            perror("free(buffers)");
            return (3);
        }
    }
    fprintf(stderr, "Successfully freed.\n");
    return (0);
}

它只分配了4 MiB的内存,并尝试通过重新分配将其大小加倍10倍。对realloc的简单调用与1 MiB块的另一个分配和一些随机写入交错,以便最小化堆分配器“技巧”。在具有16 GiB RAM的Ubuntu linux机器上,我有以下输出:

./realloc_test
initial size == 4194304
initial buffer == 0x7f3604c81010

new size == 8388608
new buffer == 0x7f3604480010

new size == 16777216
new buffer == 0x7f360347f010

new size == 33554432
new buffer == 0x7f360147e010

new size == 67108864
new buffer == 0x7f35fd47d010

new size == 134217728
new buffer == 0x7f35f547c010

new size == 268435456
new buffer == 0x7f35e547b010

new size == 536870912
new buffer == 0x7f35c547a010

new size == 1073741824
new buffer == 0x7f3585479010

new size == 2147483648
new buffer == 0x7f3505478010

new size == 4294967296
new buffer == 0x7f3405477010

Trying to free the buffers.
Successfully freed.

因此,直到4 GiB的所有重新分配似乎都成功了。但是,当我通过此命令将内核虚拟内存记帐模式设置为2(始终检查,从不过度使用)时:

echo 2 > /proc/sys/vm/overcommit_memory

然后输出变为:

./realloc_test
initial size == 4194304
initial buffer == 0x7fade1fa7010

new size == 8388608
new buffer == 0x7fade17a6010

new size == 16777216
new buffer == 0x7fade07a5010

new size == 33554432
new buffer == 0x7fadde7a4010

new size == 67108864
new buffer == 0x7fadda7a3010

new size == 134217728
new buffer == 0x7fadd27a2010

new size == 268435456
new buffer == 0x7fadc27a1010

new size == 536870912
new buffer == 0x7fada27a0010

new size == 1073741824
new buffer == 0x7fad6279f010

new size == 2147483648
tmp_pointer == (nil)
errno == 12
realloc: Cannot allocate memory

重新定位在2 GiB处失败。 top报告的当时计算机的可用内存大约为5 GiB,因此它是合理的,因为realloc必须始终分配连续的内存块。现在,让我们看看在同一台机器上的VirtualBox内的Mac OS X Lion上运行相同程序时会发生什么,但只有8 GiB的虚拟RAM:

./realloc_test
initial size == 4194304
initial buffer == 0x101c00000

new size == 8388608
tmp_pointer == 0x102100000
errno == 22
realloc: Invalid argument

这里,该程序首次重新分配到8 MiB时出现问题。在我看来,这很奇怪,因为top报告的虚拟计算机的可用内存大约为7 GiB。

但事实是,realloc实际上是成功的,因为它的返回值是非NULL(在程序终止之前注意tmp_pointer值)。但同样成功调用realloc也将errno设置为非零!现在,处理这种情况的正确方法是什么?

我应该忽略errno并仅检查realloc的返回值吗?但那么一些基于errno的错误处理程序呢?这可能不是一个好主意。

当realloc返回非NULL指针时,我应该将errno设置为零吗?这似乎是一个解决方案。但是......我看过这里:http://austingroupbugs.net/view.php?id=374。我不知道这个资源有多权威,但关于realloc,它非常明确:

“......标准也明确规定,除非记录在案,否则不能成功检查错误,......”

如果我理解正确,它会说:是的,当realloc返回NULL时你可以看看errno,但不是这样!这说,我允许将errno重置为零吗?没有看过它?我发现很难理解和决定什么是坏事,做什么好事。

我仍然无法理解为什么realloc首先设置这个错误。它的“无效论证”的价值是什么意思?它没有在手册页中列出,他们只提到了errno ENOMEM(通常是12号)。可能会出错吗?这个简单程序中的某些东西是否会在Mac OS X下导致此行为?可能是的,......所以,两个主要问题是:

  1. 有什么问题? 和
  2. 如何纠正?更确切地说:如何改进这个简单的程序,以便Mac OS X上的realloc在成功时将errno保持为零?

2 个答案:

答案 0 :(得分:7)

我认为你误解了errno的目的 - 在失败的情况下只定义 。引用POSIX标准:

  

errno的值只有在函数的返回值被指示为有效时才会被检查。本卷IEEE Std 1003.1-2001 中的无功能应将errno设置为零

(我大胆的脸)。因此,在成功通话后,您不能指望errno为0。您应该只检查返回值 - 然后您可以确定使用errno失败的原因,而不是相反(即errno的值表示存在错误条件 - 这不是它的目的 - 事实上,如果没有错误就不会触及它。)

答案 1 :(得分:0)

显然,realloc()的实现存在一个错误。读一下eglibc的realloc(https://github.com/Xilinx/eglibc/blob/master/malloc/malloc.c#L2907)的源代码,似乎问题发生在无法从主&#34;竞技场&#34;但是从次要的那个,它没有将errno值重置为零。 errno变量在该文件中永远不会设置为零。

在修复它之前,如果返回值不为null,则忽略errno == ENOMEM可能是安全的。