Подтвердить что ты не робот

Как я могу правильно обрабатывать malloc-неудачу в C, особенно когда есть более одного malloc?

Предположим, что это часть моего кода:

 int foo()
 {  
    char *p, *q ;
    if((p = malloc(BUFSIZ)) == NULL) {
        return ERROR_CODE;
    }
    if((q = malloc(BUFSIZ)) == NULL) {
        free(p)
        return ERROR_CODE;
    }
    /* Do some other work... */

    free(p);
    free(q);  
 }

Поскольку возможно, что первая malloc успешна, но вторая не удалась, я использую free(p) во втором "обработчике ошибок". Но что, если есть больше malloc, и что, если я хочу изменить код (корректируя их порядок, добавляя или удаляя некоторые malloc)?

Я знаю, что в С++ есть такие вещи, как RAII и безопасные исключения, и т.д. Но в целом, как правильно обращаться с ошибкой malloc в C? (возможно, используя некоторые goto?)

4b9b3361

Ответ 1

Ваш код в порядке, но для многих переменных я бы предпочел:

int
foo()
{
    char *p = NULL;
    char *q = NULL;
    int ret = 0;

    if (NULL == (p = malloc(BUFSIZ)))
    {
        ret = ERROR_CODE;
        goto error;
    }

    // possibly do something here

    if (NULL == (q = malloc(BUFSIZ)))
    {
        ret = ERROR_CODE;
        goto error;
    }

    // insert similar repetitions

    // hopefully do something here

  error:
    free (p);
    free (q);
    return ret;
}

Обратите внимание, что освобождение NULL определяется как no-op.

Это позволяет избежать n уровней отступа для переменных n. Вы можете очистить дескрипторы файлов и т.д. Аналогично (хотя вам нужно поставить условие вокруг close()).

Теперь, если вы знаете, что можете выделить их все сразу, тогда dasblinkenlight имеет хороший ответ, но здесь другой способ:

int
foo()
{
    int ret = 0;
    char *p = malloc(BUFSIZ);
    char *q = malloc(BUFSIZ);
    char *r = malloc(BUFSIZ);
    if (!p || !q || !r)
    {
        ret = ERROR_CODE;
        goto exit;
    }

    // do something

  exit:
    free(p);
    free(q);
    free(r);
    return ret;
}

Окончательная возможность: если вы действительно хотите выйти из программы на malloc, попробуйте использовать параметр mallopt M_CHECK_ACTION. Это приводит к проверке ошибок malloc() и вызывает abort(), возможно, распечатывает полезное сообщение.

На странице man:

NAME

mallopt - задайте параметры выделения памяти

СИНТАКСИС

  #include <malloc.h>

  int mallopt(int param, int value);

ОПИСАНИЕ

Функция mallopt() настраивает параметры, управляющие поведением функций выделения памяти (см. malloc(3)). Аргумент param указывает параметр, который необходимо изменить, и value указывает новое значение для этого параметра.

Для param могут быть заданы следующие значения:

M_CHECK_ACTION

Установка этого параметра контролирует реакцию glibc при обнаружении различных ошибок программирования (например, освобождение одного и того же указателя дважды). 3 младших значащих бита (2, 1 и 0) значения, присвоенного этому параметру, определяют поведение glibc следующим образом:

Бит 0. Если этот бит установлен, напечатайте однострочное сообщение на stderr, в котором содержится подробная информация об ошибке. Сообщение начинается с строки "*** glibc detected ***", за которой следует имя программы, имя функции выделения памяти, в которой была обнаружена ошибка, краткое описание ошибки и адрес памяти, где была обнаружена ошибка.

Бит 1. Если этот бит установлен, то после печати любого сообщения об ошибке, указанного бит 0, программа завершается вызовом abort(3). В версиях glibc с 2.4, если бит 0 также установлен, то между печатью сообщения об ошибке и прерыванием программа также печатает трассировку стека в виде backtrace(3) и печатает отображение памяти процесса в стиле /proc/[pid]/maps (см. proc(5)).

Бит 2: (поскольку glibc 2.4) Этот бит действует только в том случае, если бит 0 также установлен. Если этот бит установлен, то однострочное сообщение, описывающее ошибку, упрощается, чтобы содержать только имя функции, в которой была обнаружена ошибка, и краткое описание ошибки.

Ответ 2

Так как вполне нормально проходить NULL до free(), вы можете выделить все, что вам нужно, в "прямой линии", проверить все в один снимок, а затем освободить все, как только вы закончите, независимо от того, независимо от того, выполняете ли вы какую-либо работу:

char *p = malloc(BUFSIZ);
char *q = malloc(BUFSIZ);
char *r = malloc(BUFSIZ);
if (p && q && r) {
    /* Do some other work... */
}
free(p);
free(q);
free(r);

Это работает до тех пор, пока нет промежуточных зависимостей, т.е. у вас нет структур с многоуровневыми зависимостями. Когда вы это сделаете, неплохо определить функцию для освобождения такой структуры, не предполагая, что все блоки памяти не являются NULL.

Ответ 3

Для большого количества распределений я бы потратил время на создание диспетчера памяти, который отслеживает распределения. Таким образом, вам никогда не придется беспокоиться о утечках, независимо от того, выполняется ли функция.

Общая идея заключается в создании обертки для malloc, которая записывает успешные распределения, а затем освобождает их по запросу. Чтобы освободить память, вы просто передаете специальный размер функции-обертки. Использование размера 0 для свободной памяти подходит, если вы знаете, что ни одно из ваших фактических распределений не будет для блоков размером 0. В противном случае вы можете использовать ~0ULL в качестве размера запроса.

Вот простой пример, который позволяет до 100 распределений между frees.

#define FREE_ALL_MEM 0

void *getmem( size_t size )
{
    static void *blocks[100];
    static int count = 0;

    // special size is a request to free all memory blocks
    if ( size == FREE_ALL_MEM )
    {
        for ( int i = 0; i < count; i++ )
            free( blocks[i] );
        count = 0;
        return NULL;
    }

    // using a linked list of blocks would allow an unlimited number of blocks
    // or we could use an array that can be expanded with 'realloc'
    // but for this example, we have a fixed size array
    if ( count == 100 )
        return NULL;

    // allocate some memory, and save the pointer in the array
    void *result = malloc( size );
    if ( result )
        blocks[count++] = result;

    return result;
}

int foo( void )
{
    char *p, *q;

    if ( (p = getmem(BUFSIZ)) == NULL ) {
        return ERROR_CODE;
    }
    if ( (q = getmem(BUFSIZ)) == NULL ) {
        getmem( FREE_ALL_MEM );
        return ERROR_CODE;
    }

    /* Do some other work... */

    getmem( FREE_ALL_MEM );
    return SUCCESS_CODE;
}

Ответ 4

Это вопрос привычки, но я предпочитаю:

int returnFlag = FAILURE;

if ((p = malloc...) != NULL)
{
    if ((q = malloc..) != NULL)
    {
        // do some work
        returnFlag = SUCCESS; // success only if it is actually success

        free(q);
    }
    free(p);
}

return returnFlag; // all other variants are failure

Ответ 5

ЕСЛИ вы ожидаете выделить большое количество предметов, это может стать беспорядочным. Старайтесь избегать подхода "goto". Не из-за старой "goto is bad" этики, а потому, что этот способ действительно может лежать безумием и утечками памяти.

Это немного перебор для небольших чисел malloc, но вы можете рассмотреть что-то вроде этого:

void free_mem(void **ptrs, size_t len)
{
    for (size_t i = 0; i < len; ++i)
    {
        free(ptrs[i]);
        ptrs[i] = NULL;
    }
}

int foo(...)
{
    void *to_be_freed[N];
    int next_ptr = 0;
    for (size_t i = 0; i < N; ++i) to_be_freed[i] = NULL;

    p = malloc(..);
    if (!p)
    {
        free_mem(to_be_freed,N);
        return ERROR_CODE;
    }
    to_be_freed[next_ptr++] = p;

    // Wash, rinse, repeat, with other mallocs
    free_mem(to_be_freed,N)
    return SUCCESS;
}

В действительности вы можете, возможно, обернуть malloc чем-то, что отслеживает это. Поместите массив и размер массива в структуру и передайте это с желаемым размером выделения.

Ответ 6

Я думаю, что первый ответ является наиболее общей целью, поскольку он может использоваться для ошибок, отличных от тех, которые вызваны malloc. Однако я бы удалил gotos и использовал один проход в цикле, как это.

int foo()
{
  char *p = NULL;
  char *q = NULL;
  int ret = 0;
  do {
    if (NULL == (p = malloc(BUFSIZ)))
    {
      ret = ERROR_CODE;
      break;
    }

    // possibly do something here

    if (NULL == (q = malloc(BUFSIZ)))
    {
      ret = ERROR_CODE;
      break;
    }

    // insert similar repetitions

    // hopefully do something here
  } while(0);
  free (p);
  free (q);
  return ret;
}