c99 goto прошлая инициализация

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

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?

22
задан Michael Myers 12 May 2010 в 18:33
поделиться

7 ответов

Вы можете попросить 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 оказывается более очевидным.

17
ответ дан 29 November 2019 в 05:24
поделиться

если я компилирую этот код с флагом -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

и никаких предупреждений без оптимизации

{{1 }}
1
ответ дан 29 November 2019 в 05:24
поделиться

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

3
ответ дан 29 November 2019 в 05:24
поделиться

Как говорит 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
ответ дан 29 November 2019 в 05:24
поделиться

Хм, не потому, что новый стандарт позволяет объявлять переменные в любом месте, это всегда хорошая идея использовать их. В вашем случае я бы поступил так, как мы делали это в классическом 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
ответ дан 29 November 2019 в 05:24
поделиться

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

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

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

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

9
ответ дан 29 November 2019 в 05:24
поделиться

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

-5
ответ дан 29 November 2019 в 05:24
поделиться
Другие вопросы по тегам:

Похожие вопросы: