2012-03-05 4 views
1

У меня есть эта простая программа C:Различного поведения перераспределить в Linux и OSX

#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 блоков MiB и некоторыми случайными записями, чтобы минимизировать «трюки» распределителя кучи. На Linux машины Ubuntu с 16 GiB RAM, у меня есть следующий вывод:

./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 (всегда проверяйте, никогда не overcommit) с помощью этой команды:

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 

rellocation терпит неудачу на 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 

Здесь, эта программа имеет проблемы с очень первый realloc до 8 MiB. Это, на мой взгляд, очень странно, потому что свободная память виртуального компьютера в то время, как сообщается top, составляет около 7 GiB.

Правда, однако, что realloc фактически преуспел, поскольку его возвращаемое значение не равно NULL (обратите внимание на значение tmp_pointer непосредственно перед завершением программы). Но тот же самый успешный вызов realloc также установил errno в ненулевое значение! Теперь, каков правильный способ справиться с этой ситуацией?

Должен ли я просто игнорировать errno и проверять только возвращаемое значение из realloc? Но тогда как насчет некоторых следующих обработчиков ошибок, основанных на ошибках? Это, вероятно, не очень хорошая идея.

Должен ли я устанавливать errno в ноль, когда realloc возвращает указатель не-NULL? Кажется, это решение. Но ... Я посмотрел здесь: http://austingroupbugs.net/view.php?id=374. Я не знаю, насколько авторитетным является этот ресурс, но в отношении realloc это очень ясно:

«... стандарт также явственен, что errno не может быть проверен при успешном завершении, если не зарегистрировано, ...»

Если я правильно понимаю, в нем говорится: Да, когда realloc возвращает NULL, вы можете посмотреть на errno, но не иначе! Это сказало, я могу сбросить errno к нулю? Не смотря на это? Мне очень непонятно понимать и решать, что плохо, и что хорошо делать.

Я все еще не могу понять, почему realloc устанавливает это errno в первую очередь. И что означает его значение «Недопустимый аргумент»? Он не указан в man-страницах, они упоминают только errno ENOMEM (обычно число 12). Может что-то пойдет не так? Что-то в этой простой программе вызывает это поведение в Mac OS X? Вероятно, да, ... так что два основных вопроса:

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

ответ

7

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

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

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

+0

Хорошо, я понимаю, что 'errno' определяется только в случае сбоя. Это имеет смысл. –

+0

Также имеет смысл сначала проверить возвращаемое значение realloc и только проверять 'errno' в случае сбоя realloc (возвращаемое значение NULL). –

+0

Но я действительно должен не соглашаться с тем, что вы сказали об успешном вызове realloc и errno, который был нетронутым (не обязательно нулевым, но неизменным). Как я уже сказал в своем вопросе, в Mac OS X Lion это не всегда верно. Вопрос в том, что вызывает это странное поведение и как его избежать. –

0

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

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

Смежные вопросы