我有这个简单的 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,这点很清楚:
“......标准也明确指出,除非记录在案,否则无法在成功时检查 errno,......”
如果我理解正确,它会说:是的,当 realloc 返回 NULL 时,您可以查看 errno,否则不能!这就是说,我可以将 errno 重置为零吗?从来不看?我发现很难理解和决定什么是坏的,什么是好的。
我仍然不明白为什么 realloc 首先设置这个 errno。它的“无效参数”值是什么意思?它没有在手册页中列出,他们只提到 errno ENOMEM(通常是 12 号)。会不会出什么问题?这个简单程序中的某些东西是否会在 Mac OS X 下导致这种行为?可能是的,......所以,两个主要问题是:
- 怎么了?和
- 如何纠正它?更准确地说:如何改进这个简单的程序,以便 Mac OS X 上的 realloc 在成功时将 errno 保持为零?