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)
может выходить из строя.
Я ожидал бы/надеюсь, что это будет рассматриваться так же, как в С++, где компилятор не позволяет переходу перейти к инициализации.
Мой вопрос: прыгает через инициализацию, разрешенную стандартом, или это ошибка в gcc-реализации c99?
Ответы
Ответ 1
Вы можете попросить gcc предупредить вас, когда вы переходите через определение переменной с помощью -Wjump-misses-init
, а затем вы можете использовать -Werror
(или, точнее, -Werror=jump-misses-init
), чтобы заставить пользователей справиться с этим. Это предупреждение включено в -Wc++-compat
, поэтому разработчики gcc знают, что код ведет себя по-разному в 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;
}
... и просто сохраняйте метки сопряжения с инициализированными переменными. У вас будет такая же проблема с вызовом многих других функций с унифицированными переменными, бесплатно просто становится более очевидным.
Ответ 2
Подобный скачок действительно разрешен стандартом, поэтому это не ошибка в GCC. Стандарт перечисляет эту ситуацию как рекомендуемое предупреждение в Приложении I.
Единственное ограничение, наложенное на скачки в C99 в отношении области видимости, заключается в том, что незаконно переходить в область видимости переменной изменяемого типа, например VLA
int main() {
int n = 5;
goto label; // <- ERROR: illegal jump
int a[n];
label:;
}
Другими словами, неверно говорить, что "прыжок - это просто прыжок в C". Переходы несколько ограничены, когда дело доходит до ввода области переменных, хотя и не так строго, как в С++. Ситуация, которую вы описываете, не является одним из ограниченных.
Ответ 3
Это не ошибка в gcc. Скачок - это просто прыжок в C. Нет специальной логики. Проблема в том, что вы сначала не инициализируете свои указатели на NULL
. Если бы вы это сделали, тогда бесплатный звонок был бы free(NULL)
, который не сработает. Запустите функцию с помощью char *p1 = NULL, *p2 = NULL;
, и все будет хорошо.
Ответ 4
Хм, это не потому, что новый стандарт допускает объявления переменных в любом месте, где всегда есть хорошая идея его использовать. В вашем случае я бы сделал, как мы сделали это в классическом 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;
}
Ответ 5
если я скомпилирую этот код с флагом -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
и без предупреждения без оптимизации
Ответ 6
Как 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;
}
Это стандартный шаблон - "ранние ошибки" вызывают переход к более поздней части кода выхода ошибки.
Ответ 7
Использование gotos - не умная идея, и вы только что нашли одну причину. Вы должны вызвать функцию обработки ошибок для каждой отдельной ошибки.