1

我有这个简单的 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 时存在问题。这在我看来很奇怪,因为当时报告的虚拟计算机的可用内存在top7 GiB 左右。

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

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

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

“......标准也明确指出,除非记录在案,否则无法在成功时检查 errno,......”

如果我理解正确,它会说:是的,当 realloc 返回 NULL 时,您可以查看 errno,否则不能!这就是说,我可以将 errno 重置为零吗?从来不看?我发现很难理解和决定什么是坏的,什么是好的。

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

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

2 回答 2

9

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

只有当函数的返回值表明它是有效的时,才应该检查errno的值。本卷 IEEE Std 1003.1-2001 中的任何功能均不得将 errno 设置为零

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

于 2012-03-05T01:44:21.160 回答
1

显然,realloc() 的实现中存在一个错误。阅读一下 eglibc 的 realloc 的源代码(https://github.com/Xilinx/eglibc/blob/master/malloc/malloc.c#L2907),当无法从主“竞技场”获取内存时,似乎会出现问题" 但从第二个开始,它不会将 errno 值重置为零。该文件中的 errno 变量永远不会设置为零。

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

于 2015-12-03T15:17:44.360 回答