Как освободить ранее выделенную память при возникновении ошибок? - PullRequest
5 голосов
/ 21 сентября 2011

Учитывая объявление функции, подобное этому:

int base_address(zval *object, int add_prefix, char **base_address TSRMLS_DC) {    
    int result;

    char *host;
    long port;
    char *prefix;  

    host = ... get host from object ...;
    port = ... get port from object ...;
    prefix = ... get prefix from object ...;

    result = SUCCESS;

    if (asprintf(base_address, "%s:%ld/%s", host, port, prefix) < 0) {
        result = FAILURE;
    }

    return result;
}

void my_func() {
    char *base_address;
    char *ping_url;

    if (base_address(getThis(), 0, &base_address TSRMLS_CC) == FAILURE) {
        MALLOC_ERROR();
    }

    if (asprintf(&ping_url, "%s/ping", base_address) < 0) {
        MALLOC_ERROR();
    }

   ... do some stuff with base address ...

    // release both, as everything worked
    free(base_address);
    free(ping_url);
}

Если первый вызов base_address завершился успешно, а второй вызов asprintf () завершился неудачно, как я могу чисто пропустить до конца функции, чтобы безопасно освободить выделенную память?

Существует ли какой-то стандартный шаблон, как избежать утечек памяти в таких ситуациях, когда память выделяется одна за другой (и каждое выделение может произойти сбой) без слишком большого дублирования кода или операторов goto?

Ответы [ 4 ]

9 голосов
/ 21 сентября 2011

Не бойся goto. Это самый простой, чистый и понятный способ обработки исключений в C:

  • Вы не повторяетесь. Дублированный код подвержен ошибкам.

  • Вы не создаете глубоко вложенный код. Глубокое вложение неразборчиво.

  • Вы не прячетесь за do {...} while (0) и break. Хороший код говорит, что это значит.

Вот базовый пример:

int operation() {

    int result = SUCCESS;

    if ((result = may_fail_first()) == FAILURE) {
        goto failed_first;
    }

    if ((result = may_fail_second()) == FAILURE) {
        goto failed_second;
    }

    // If your cleanup code doesn't ordinarily need to run.
    goto end;

failed_second:
    cleanup_second();

    // If you don't need to clean up everything.
    goto end;

failed_first:
    cleanup_first();

end:
    return result;

}
7 голосов
/ 21 сентября 2011

Это один из нормальных способов использования goto для обработки ошибок:

if (base_address(getThis(), 0, &base_address TSRMLS_CC) == FAILURE) {
    goto end;
}

if (asprintf(&ping_url, "%s/ping", base_address) < 0) {
    goto release_address;
}

// do stuff

release_address:
free(base_address);
end:

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

Вы можете обратиться к другому из моих ответов здесь , в котором говорится об общем случае.

2 голосов
/ 21 сентября 2011

Если NULL присваивается переменным-указателям во время объявления, тогда free сможет обрабатывать случаи, когда они никогда не malloc также редактировались (это просто ничего не сделает). (Можно использовать и другие средства защиты, например, -1 для ссылки на недопустимый fd.)

Я использую это в сочетании с с использованием goto для (дополнительной) очистки - некоторые другие ответы кажутся мне "многословными". Я считаю, что goto нужно использовать разумно, чтобы избежать спагетти-кода, и, в общем, я нахожу, что "гуси вокруг гото" слишком сложно отслеживать постоянно. Дополнительное назначение NULL в первую очередь также позволяет использовать сами переменные в качестве проверки (во время любой возможной очистки или иным образом).

Удачного кодирования.


Пример:

void my_func() {
    char *base_address = NULL;
    char *ping_url = NULL;

    if (base_address(getThis(), 0, &base_address TSRMLS_CC) == FAILURE) {
        goto cleanup;
    }

    if (asprintf(&ping_url, "%s/ping", base_address) < 0) {
        goto cleanup;
    }

    // stuff... and assign return value (or return directly) if applicable,
    // assign NULL to variables which contain values that should
    // not be free'd, etc (use as guards!).
    // I prefer to add guards into the cleanup vs. "skip over it".
    // I vary rarely, if ever, have multiple cleanup sections -- in most
    // cases this would indicate the need for additional function(s) to me.

  cleanup:
    free(base_address);
    if (ping_url) {
       // perhaps need additional cleanup        
       free(ping_url);
    }

    // Return value, if applicable. (If a "direct return" above isn't
    // advisable for the given function due to cleanup rules.)
}
2 голосов
/ 21 сентября 2011

в c, вам достаточно сделать if () для обработки ошибок

в вашем случае

if ( base_address( ... ) )
{
  if (asprint( ... ))
  {
     /// do some stuff 

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