Различное поведение realloc в linux и osx - PullRequest
1 голос
/ 05 марта 2012

У меня есть эта простая программа на 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? Вероятно, да, ... так, два основных вопроса:

  1. Что не так? и
  2. Как это исправить? Точнее: как улучшить эту простую программу, чтобы при успешной работе realloc в Mac OS X оставлял значение errno равным нулю?

Ответы [ 2 ]

7 голосов
/ 05 марта 2012

Я думаю, вы неправильно понимаете цель errno - это только , определенное в случае сбоя.Чтобы процитировать стандарт POSIX:

Значение errno следует проверять только в том случае, если оно указано действительным возвращаемым значением функции. Нет функции в этом томе IEEE Std 1003.1-2001 должен установить errno на ноль .

(выделено мной жирным шрифтом).Таким образом, вы не можете ожидать, что errno будет 0 после успешного вызова.Вы должны только проверить возвращаемое значение - тогда вы можете определить, почему оно не удалось, используя errno, но не наоборот (т. Е. Значение errno не не указывает на наличие условия ошибки- это не является его целью - фактически, это не затронуто, если нет ошибки).

0 голосов
/ 03 декабря 2015

Видимо, есть ошибка в реализации realloc (). Считывая немного исходный код realloc eglibc (https://github.com/Xilinx/eglibc/blob/master/malloc/malloc.c#L2907)), возникает проблема, когда память не может быть получена из основной «арены», а из вторичной, что она не сбрасывает значение errno на ноль. Переменная errno никогда не устанавливается в ноль в этом файле.

Пока они не исправят это, вы, вероятно, в безопасности, проигнорировав errno == ENOMEM, если возвращаемое значение не равно нулю.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...