У меня есть эта простая программа на 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 МБ памяти и 10 раз пытается удвоить свой размер путем перераспределения. Простые вызовы realloc чередуются с другими распределениями блоков по 1 МБ и некоторыми случайными записями, чтобы минимизировать «хитрости» распределителя кучи. На машине Ubuntu Linux с 16 ГБ ОЗУ у меня есть следующий вывод:
./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 ГиБ, похоже, удаются. Однако, когда я устанавливаю режим учета виртуальной памяти ядра на 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 ГиБ. Как сообщалось в top
, свободная память компьютера в тот момент составляла около 5 ГиБ, поэтому это разумно, поскольку realloc всегда должен выделять непрерывный блок памяти. Теперь давайте посмотрим, что происходит при запуске той же программы в Mac OS X Lion внутри VirtualBox на той же машине, но только с 8 ГБ виртуальной памяти:
./realloc_test
initial size == 4194304
initial buffer == 0x101c00000
new size == 8388608
tmp_pointer == 0x102100000
errno == 22
realloc: Invalid argument
Здесь эта программа имеет проблему с самой первой пересылкой до 8 МБ. Это, на мой взгляд, очень странно, потому что свободная память виртуального компьютера в то время, как сообщал top
, составляла около 7 ГиБ.
Правда в том, что realloc действительно успешно завершился, поскольку его возвращаемое значение не NULL (обратите внимание на значение tmp_pointer
непосредственно перед завершением программы). Но тот же самый успешный вызов realloc также установил errno в ненулевое значение! Теперь, как правильно справиться с этой ситуацией?
Должен ли я просто игнорировать ошибку и проверять только возвращаемое значение из realloc? Но тогда как насчет некоторых следующих обработчиков ошибок на основе ошибок? Это, вероятно, не очень хорошая идея.
Должен ли я установить значение errno равным нулю, когда realloc возвращает ненулевой указатель? Похоже, это решение. Но ... Я посмотрел здесь: http://austingroupbugs.net/view.php?id=374. Я не знаю, насколько авторитетен этот ресурс, но в отношении realloc, это очень ясно об этом:
"... стандарт также явный, что errno не может быть проверен в случае успеха, если не задокументировано, ..."
Если я правильно понимаю, это говорит: Да, когда realloc возвращает NULL, вы можете посмотреть на errno, но не иначе! При этом я могу сбросить errno на ноль? Даже не глядя на это? Мне очень непонятно понимать и решать, что плохо, а что хорошо.
Я до сих пор не могу понять, почему realloc вообще устанавливает это errno. И что означает его значение «Неверный аргумент»? Он не указан в справочных страницах, они упоминают только errno ENOMEM (обычно номер 12). Может ли что-то пойти не так? Что-то в этой простой программе вызывает такое поведение под Mac OS X? Вероятно, да, ... так, два основных вопроса:
- Что не так?
и
- Как это исправить? Точнее: как улучшить эту простую программу, чтобы при успешной работе realloc в Mac OS X оставлял значение errno равным нулю?