Ответ 1
Он успешно компилируется, потому что GCC разрешает его как расширение GNU, даже если оно не является частью стандарта C89 или ANSI. Если вы хотите строго придерживаться этих стандартов, вы должны передать флаг -pedantic
.
Я давно думал, что в C все переменные должны были быть объявлены в начале функции. Я знаю, что в C99 правила те же, что и в С++, но каковы правила размещения объявлений переменных для C89/ANSI C?
Следующий код успешно компилируется с gcc -std=c89
и gcc -ansi
:
#include <stdio.h>
int main() {
int i;
for (i = 0; i < 10; i++) {
char c = (i % 95) + 32;
printf("%i: %c\n", i, c);
char *s;
s = "some string";
puts(s);
}
return 0;
}
Должны ли объявления c
и s
вызывать ошибку в режиме C89/ANSI?
Он успешно компилируется, потому что GCC разрешает его как расширение GNU, даже если оно не является частью стандарта C89 или ANSI. Если вы хотите строго придерживаться этих стандартов, вы должны передать флаг -pedantic
.
Для C89 вы должны объявить все свои переменные в начале блока области.
Итак, ваше объявление char c
является допустимым, так как оно находится в верхней части блока видимости цикла цикла. Но объявление char *s
должно быть ошибкой.
От ремонтопригодности, а не синтаксической точки зрения, есть по крайней мере три поезда мысли:
Объявите все переменные в начале функции, чтобы они были в одном месте, и вы сможете увидеть полный список с первого взгляда.
Объявите все переменные как можно ближе к месту, в котором они впервые используются, поэтому вы будете знать, почему каждый из них необходим.
Объявите все переменные в начале самого внутреннего блока области видимости, чтобы они как можно скорее вышли из сферы действия и позволили компилятору оптимизировать память и сказать вам, случайно ли вы используете их там, где у вас не было предназначен.
Обычно я предпочитаю первый вариант, так как я нахожу, что другие часто заставляют меня искать код для объявлений. Определение всех переменных спереди также упрощает инициализацию и наблюдение за ними из отладчика.
Я иногда объявляю переменные в блоке меньшего объема, но только для Хорошего Разума, которого у меня очень мало. Один пример может быть после fork()
, чтобы объявлять переменные, необходимые только дочернему процессу. Для меня этот визуальный индикатор является полезным напоминанием о своей цели.
Группировка объявлений переменных в верхней части блока является наследием, вероятно, из-за ограничений старых примитивных компиляторов C. Все современные языки рекомендуют, а иногда даже применяют декларацию локальных переменных в последней точке: где они сначала инициализируются. Потому что это избавляет от риска случайного использования по ошибке. Разделение декларации и инициализации также предотвращает использование "const" (или "final" ), когда вы могли.
К сожалению, С++ продолжает принимать старый, верхний способ декларации для обратной совместимости с C (одна совместимость с C из многих других...) Но С++ пытается отойти от него:
C99 начинает перемещать C в этом же направлении.
Если вы беспокоитесь о том, что не обнаруживаете локальные переменные, то это означает, что у вас есть гораздо большая проблема: блокирующий блок слишком длинный и должен быть разделен.
Как отмечают другие, GCC разрешает в этом отношении (и, возможно, другие компиляторы, в зависимости от аргументов, с которыми они звонят) даже в режиме "C89", если вы не используете "педантичную" проверку. Честно говоря, нет основополагающих причин не иметь педантичности; качественный современный код должен всегда компилироваться без предупреждений (или очень немногих, где вы знаете, что делаете что-то конкретное, что подозрительно для компилятора, как возможная ошибка), поэтому, если вы не можете сделать свой код компиляцией с педантичной настройкой, ему, вероятно, потребуется некоторое внимание.
C89 требует, чтобы переменные были объявлены перед любыми другими операторами в каждой области, более поздние стандарты разрешают объявление ближе к использованию (что может быть как более интуитивно понятным, так и более эффективным), особенно одновременное объявление и инициализация переменной управления циклом в 'for '.
Как уже отмечалось, в этом есть две школы мысли.
1) Объявите все в верхней части функций, так как в 1987 году.
2) Объявите ближайший к первому использованию и в наименьшей возможной области.
Мой ответ на этот вопрос DO DOOT! Позвольте мне объяснить:
Для длинных функций 1) делает рефакторинг очень сложным. Если вы работаете в кодовой базе, где разработчики против идеи подпрограмм, тогда у вас будет 50 объявлений переменных в начале функции, а некоторые из них могут быть просто "i" для цикла, который на самом нижней части функции.
Поэтому я разработал декларацию по-верхнему ПТСР и попытался сделать вариант 2) религиозно.
Я вернулся к первому варианту из-за одной вещи: коротких функций. Если ваши функции достаточно короткие, то у вас будет мало локальных переменных, и поскольку функция короткая, если вы поместите их в начало функции, они все равно будут близки к первому использованию.
Кроме того, анти-шаблон "объявить и установить в NULL", когда вы хотите объявить вверху, но вы не сделали некоторые вычисления, необходимые для инициализации, разрешен, потому что вещи, которые вам нужно инициализировать, скорее всего, будут получены как аргументы.
Итак, теперь я думаю, что вы должны объявить в верхней части функций и как можно ближе к первому использованию. Так что ОБ! И способ сделать это с хорошо разделенными подпрограммами.
Но если вы работаете над длинной функцией, тогда поместите вещи, наиболее близкие к первому использованию, потому что таким образом будет легче извлекать методы.
Мой рецепт - это. Для всех локальных переменных возьмите переменную и переместите объявление в нижнюю часть, скомпилируйте, а затем переместите объявление непосредственно перед ошибкой компиляции. Это первое использование. Сделайте это для всех локальных переменных.
int foo = 0;
<code that uses foo>
int bar = 1;
<code that uses bar>
<code that uses foo>
Теперь определите блок области видимости, который начинается до объявления и перемещает конец до тех пор, пока программа не компилирует
{
int foo = 0;
<code that uses foo>
}
int bar = 1;
<code that uses bar>
>>> First compilation error here
<code that uses foo>
Это не компилируется, потому что есть еще один код, который использует foo. Мы можем заметить, что компилятор смог пройти через код, который использует бар, потому что он не использует foo. На данный момент существует два варианта. Механический - просто переместить "}" вниз, пока он не скомпилируется, а другой вариант - проверить код и определить, можно ли изменить порядок:
{
int foo = 0;
<code that uses foo>
}
<code that uses foo>
int bar = 1;
<code that uses bar>
Если порядок может быть переключен, возможно, что вы хотите, потому что это сокращает срок службы временных значений.
Еще одно замечание: нужно ли сохранять значение foo между блоками кода, которые его используют, или же это может быть просто другое foo в обоих. Например
int i;
for(i = 0; i < 8; ++i){
...
}
<some stuff>
for(i = 3; i < 32; ++i){
...
}
Эти ситуации требуют больше, чем моя процедура. Разработчику придется проанализировать код, чтобы определить, что делать.
Но первым шагом является поиск первого использования. Вы можете сделать это визуально, но иногда просто проще удалить декларацию, попытаться скомпилировать и просто вернуть ее выше первого использования. Если это первое использование находится внутри оператора if, поместите его туда и проверьте, компилируется ли он. Затем компилятор идентифицирует другие виды использования. Попытайтесь создать блок видимости, который включает оба применения.
После выполнения этой механической части становится легче анализировать, где находятся данные. Если переменная используется в блоке большой области, проанализируйте ситуацию и посмотрите, используете ли вы одну и ту же переменную для двух разных вещей (например, "i", которая используется для двух циклов). Если использование не связано, создайте новые переменные для каждого из этих несвязанных применений.
Я приведу некоторые инструкции из руководства для gcc версии 4.7.0 для пояснения.
"Компилятор может принимать несколько базовых стандартов, таких как" c90 "или" С++ 98 ", и диалекты GNU этих стандартов, такие как" gnu90 "или" gnu ++ 98 ". Указав базовый стандарт, компилятор будет принять все программы, следующие за этим стандартом, и те, которые используют расширения GNU, которые не противоречат ему. Например, '-std = c90 отключает некоторые функции GCC, которые несовместимы с ISO C90, такие как ключевые слова asm и typeof, но не другие GNU расширения, которые не имеют значения в ISO C90, например, исключая средний термин выражения:.
Я думаю, что ключевым моментом вашего вопроса является то, почему gcc не соответствует C89, даже если используется опция "-std = c89" . Я не знаю версию вашего gcc, но я думаю, что не будет большой разницы. Разработчик gcc сказал нам, что опция "-std = c89" просто означает, что расширения, которые противоречат C89, отключены. Таким образом, он не имеет ничего общего с некоторыми расширениями, которые не имеют значения в C89. И расширение, которое не ограничивает размещение объявления переменной, относится к расширениям, которые не противоречат C89.
Честно говоря, все будут думать, что он должен полностью соответствовать C89 с первого взгляда на вариант "-std = c89" . Но это не так. Что касается проблемы, объявляющей все переменные в начале, то лучше или хуже, это просто привычка.