C99 Перейти к инициализации - PullRequest
22 голосов
/ 12 мая 2010

При отладке сбоя я столкнулся с этой проблемой в некотором коде:

int func()
{
    char *p1 = malloc(...);
    if (p1 == NULL)
        goto err_exit;

    char *p2 = malloc(...);
    if (p2 == NULL)
        goto err_exit;

    ...

err_exit:
    free(p2);
    free(p1);

    return -1;
}

Проблема возникает, когда происходит сбой первого malloc.Поскольку мы перепрыгиваем через инициализацию p2, она содержит случайные данные, и вызов free(p2) может привести к сбою.

Я ожидаю / надеюсь, что это будет обрабатываться так же, как в C ++, где компиляторне позволяет goto перепрыгивать через инициализацию.

Мой вопрос: перепрыгивает ли инициализация, разрешенная стандартом, или это ошибка в реализации gcc для c99?

Ответы [ 7 ]

17 голосов
/ 12 мая 2010

Вы можете попросить gcc предупредить вас, когда вы перепрыгиваете через определение переменной, используя -Wjump-misses-init, а затем вы можете использовать -Werror (или, точнее, -Werror=jump-misses-init), чтобы заставить пользователей справиться с этим. Это предупреждение включено в -Wc++-compat, поэтому разработчики gcc знают, что код ведет себя по-разному в C и C ++.

Вы также можете немного изменить код:

int func()
{
    char *p1 = malloc(...);
    if (p1 == NULL)
        goto err_exit_1;

    char *p2 = malloc(...);
    if (p2 == NULL)
        goto err_exit_2;

    ...

err_exit_2:
    free(p2);
err_exit_1:
    free(p1);

    return -1;
}

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

9 голосов
/ 12 мая 2010

Такой переход действительно разрешен стандартом, так что это не ошибка в GCC. Стандарт перечисляет эту ситуацию в качестве рекомендуемого предупреждения в Приложении I.

Единственное ограничение, налагаемое на переходы в C99 в отношении области видимости, состоит в том, что незаконно переходить в область видимости переменной с изменяемым типом, например VLA

int main() {
  int n = 5;
  goto label; // <- ERROR: illegal jump
  int a[n];
label:;
}

Другими словами, неправильно говорить, что «прыжок - это просто прыжок в Си». Переходы несколько ограничены, когда дело доходит до ввода переменной области, хотя и не так строго, как в C ++. Ситуация, которую вы описываете, не является одной из ограниченных.

3 голосов
/ 13 мая 2010

Хм, это не потому, что новый стандарт допускает объявления переменных где угодно, поэтому всегда полезно его использовать. В вашем случае я бы поступил так, как мы это делали в классическом C.

int func()
{
char *p1 = NULL;    /* So we have a definite value */
char *p2 = NULL;

  p1 = malloc(...);
  if(!p1)
    goto err_exit;

  p2 = malloc(...);
  if(!p2)
    goto err_exit;

  ...

  err_exit:
    free(p2);
    free(p1);

  return -1;
}
3 голосов
/ 12 мая 2010

Это не ошибка в gcc.Прыжок - это просто прыжок в C. Специальной логики не применено.Проблема в том, что вы сначала не инициализируете свои указатели на NULL.Если бы вы сделали это, то ваш бесплатный звонок был бы free(NULL), который не вылетал бы.Запустите функцию с char *p1 = NULL, *p2 = NULL; и все будет хорошо.

1 голос
/ 12 мая 2010

если я скомпилирую этот код с флагом -O2

gcc -Wall -std=c99 -O2 jump.c

У меня предупреждение:

jump.c: In function ‘func’:
jump.c:10: warning: ‘p2’ may be used uninitialised in this function

и без предупреждения без оптимизации

0 голосов
/ 13 мая 2010

Как AndreyT говорит , перепрыгивание через инициализацию разрешено C99. Вы можете исправить свою логику, используя отдельные метки для двух сбоев:

int func()
{
    char *p1 = malloc(...);
    if (p1 == NULL)
        goto err_exit_p1;

    char *p2 = malloc(...);
    if (p2 == NULL)
        goto err_exit;

    ...

err_exit:
    free(p2);
err_exit_p1:
    free(p1);

    return -1;
}

Это стандартный шаблон - «ранние ошибки» вызывают переход к более поздней части кода завершения ошибки.

0 голосов
/ 12 мая 2010

Использование gotos не является разумной идеей, и вы только что нашли причину. Вы должны вызывать функцию обработки ошибок для каждой отдельной ошибки.

...