我有这个简单的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下导致此行为?可能是的,......所以,两个主要问题是:
答案 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可能是安全的。