Я долго думал, что в C все переменные нужно объявлять в начале функции. Я знаю, что в C99 правила такие же, как в C ++, но каковы правила размещения объявления переменных для 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?
c
declaration
c89
mcjabberz
источник
источник
Ответы:
Он успешно компилируется, поскольку GCC позволяет
s
объявлять расширение GNU, даже если оно не является частью стандартов C89 или ANSI. Если вы хотите строго придерживаться этих стандартов, вы должны передать этот-pedantic
флаг.Объявление
c
в начале{ }
блока является частью стандарта C89; блок не обязательно должен быть функцией.источник
s
является расширением (с точки зрения C89). Объявлениеc
совершенно законно в C89, никаких расширений не требуется.Для C89 вы должны объявить все свои переменные в начале блока области видимости .
Итак, ваше
char c
объявление действительно, так как оно находится в верхней части блока области видимости цикла. Ноchar *s
объявление должно быть ошибкой.источник
gcc
. То есть не верьте, что программа может быть скомпилирована, что означает, что она соответствует требованиям.Группировка объявлений переменных в верхней части блока является устаревшей, вероятно, из-за ограничений старых, примитивных компиляторов C. Все современные языки рекомендуют, а иногда даже предписывают объявление локальных переменных в самый последний момент: там, где они впервые инициализируются. Потому что это избавляет от риска ошибочного использования случайного значения. Разделение объявления и инициализации также не позволяет вам использовать «const» (или «final»), когда вы могли бы.
C ++, к сожалению, продолжает принимать старый способ декларирования обратной совместимости с C (одна проблема совместимости с C выделяется из многих других ...), но C ++ пытается отойти от нее:
C99 начинает перемещать C в том же направлении.
Если вы беспокоитесь о том, что не можете найти, где объявлены локальные переменные, это означает, что у вас есть гораздо более серьезная проблема: охватывающий блок слишком длинный и должен быть разделен.
https://wiki.sei.cmu.edu/confluence/display/c/DCL19-C.+Minimize+the+scope+of+variables+and+functions
источник
int y; ... if (x) { printf("X was true"); y=23;} return y;
...null
, часто полезно перехватить все биты-ноль. Кроме того, в языках, в которых явно указано, что переменные по умолчанию имеют нулевые биты, использование этого значения не является ошибкой . Составители не все же имеют тенденцию становиться слишком дурацкие с их «оптимизации», но компилятор авторы продолжают пытаться получить больше и больше умных. Параметр компилятора для инициализации переменных с помощью преднамеренных псевдослучайных переменных может быть полезен для выявления ошибок, но просто оставление хранилища с его последним значением иногда может маскировать ошибки.С точки зрения ремонтопригодности, а не с синтаксической точки зрения, существует как минимум три направления мысли:
Объявите все переменные в начале функции, чтобы они были в одном месте, и вы могли сразу увидеть полный список.
Объявите все переменные как можно ближе к месту их первого использования, чтобы вы знали, зачем каждая из них нужна.
Объявите все переменные в начале самого внутреннего блока области видимости, чтобы они вышли из области видимости как можно скорее и позволили компилятору оптимизировать память и сообщить вам, если вы случайно используете их там, где не планировали.
Я обычно предпочитаю первый вариант, так как другие часто заставляют меня искать объявления в коде. Предварительное определение всех переменных также упрощает инициализацию и наблюдение за ними из отладчика.
Иногда я объявляю переменные в меньшем блоке области видимости, но только по уважительной причине, которой у меня очень мало. Один из примеров может быть после a
fork()
для объявления переменных, необходимых только дочернему процессу. Для меня этот визуальный индикатор - полезное напоминание об их назначении.источник
Как отмечали другие, GCC в этом отношении разрешает (и, возможно, другие компиляторы, в зависимости от аргументов, с которыми они вызываются) даже в режиме «C89», если вы не используете «педантичную» проверку. Если честно, не так много веских причин не проявлять педантичность; качественный современный код всегда должен компилироваться без предупреждений (или очень мало, если вы знаете, что делаете что-то конкретное, что подозрительно для компилятора как возможная ошибка), поэтому, если вы не можете скомпилировать свой код с помощью педантичной настройки, это, вероятно, требует некоторого внимания.
C89 требует, чтобы переменные объявлялись перед любыми другими операторами в каждой области, более поздние стандарты разрешают более близкое к использованию объявление (которое может быть как более интуитивным, так и более эффективным), особенно одновременное объявление и инициализацию переменной управления циклом в циклах for.
источник
Как уже отмечалось, на этот счет существуют две точки зрения.
1) Объявите все в верхней части функций, потому что год 1987.
2) Объявите как можно ближе к первому использованию и в минимально возможном объеме.
Мой ответ - ДЕЛАЙТЕ ОБОИХ! Позволь мне объяснить:
Для длинных функций 1) очень затрудняет рефакторинг. Если вы работаете в кодовой базе, где разработчики выступают против идеи подпрограмм, тогда у вас будет 50 объявлений переменных в начале функции, и некоторые из них могут быть просто «i» для цикла for, который находится в самом начале внизу функции.
Поэтому из этого я развил посттравматическое стрессовое расстройство и попытался применить вариант 2) неукоснительно.
Я вернулся к первому варианту по одной причине: короткие функции. Если ваши функции достаточно короткие, то у вас будет несколько локальных переменных, и, поскольку функция короткая, если вы поместите их в начало функции, они все равно будут близки к первому использованию.
Кроме того, анти-шаблон «объявить и установить в NULL», когда вы хотите объявить вверху, но вы не выполнили некоторые вычисления, необходимые для инициализации, разрешается, потому что вещи, которые вам нужно инициализировать, скорее всего, будут получены в качестве аргументов.
Итак, теперь я считаю, что вы должны объявлять функции в верхней части и как можно ближе к первому использованию. Так что ОБА! И способ сделать это - использовать хорошо разделенные подпрограммы.
Но если вы работаете над длинной функцией, то лучше всего используйте ее в первую очередь, потому что так будет легче извлекать методы.
Мой рецепт такой. Для всех локальных переменных возьмите переменную и переместите ее объявление в конец, скомпилируйте, а затем переместите объявление непосредственно перед ошибкой компиляции. Это первое использование. Сделайте это для всех локальных переменных.
Теперь определите блок области видимости, который начинается перед объявлением, и переместите конец, пока программа не скомпилируется.
Это не компилируется, потому что есть еще код, использующий foo. Мы можем заметить, что компилятор смог пройти через код, который использует bar, потому что он не использует foo. На данный момент есть два варианта. Механический - просто переместить "}" вниз до тех пор, пока он не скомпилируется, а другой вариант - проверить код и определить, можно ли изменить порядок на:
Если порядок можно изменить, вероятно, это то, что вам нужно, потому что это сокращает срок службы временных значений.
Еще одно замечание: нужно ли сохранять значение foo между блоками кода, которые его используют, или это может быть просто другой foo в обоих. Например
Эти ситуации требуют большего, чем моя процедура. Разработчик должен будет проанализировать код, чтобы определить, что делать.
Но первый шаг - найти первое применение. Вы можете сделать это визуально, но иногда проще удалить объявление, попытаться скомпилировать и просто вернуть его выше первого использования. Если это первое использование находится внутри оператора if, поместите его туда и проверьте, компилируется ли он. Затем компилятор определит другие варианты использования. Попробуйте создать блок области видимости, охватывающий оба использования.
После того, как эта механическая часть сделана, становится легче анализировать, где находятся данные. Если переменная используется в большом блоке области видимости, проанализируйте ситуацию и посмотрите, используете ли вы одну и ту же переменную для двух разных вещей (например, «i», который используется для двух циклов for). Если использования не связаны, создайте новые переменные для каждого из этих несвязанных видов использования.
источник
Вы должны объявить все переменные вверху или «локально» в функции. Ответ:
Это зависит от того, какую систему вы используете:
1 / Встроенная система (особенно связанная с такими жизнями, как самолет или автомобиль): она позволяет использовать динамическую память (например: calloc, malloc, new ...). Представьте, что вы работаете над очень большим проектом с 1000 инженерами. Что делать, если они выделяют новую динамическую память и забыли ее удалить (когда она больше не используется)? Если встроенная система будет работать долгое время, это приведет к переполнению стека и повреждению программного обеспечения. Убедиться в качестве не просто (лучше всего запретить динамическую память).
Если самолет работает в течение 30 дней и не выключается, что произойдет, если программное обеспечение будет повреждено (когда самолет все еще находится в воздухе)?
2 / Другие системы, такие как Интернет, ПК (имеют большой объем памяти):
Вы должны объявить переменную «локально», чтобы оптимизировать использование памяти. Если эти системы работают долгое время и происходит переполнение стека (из-за того, что кто-то забыл удалить динамическую память). Просто сделайте простую вещь, чтобы перезагрузить компьютер: P Это не влияет на жизнь
источник
malloc()
. Хотя я никогда не видел устройства, которое не способно на это, лучше избегать динамического распределения во встроенных системах ( см. Здесь ). Но это не имеет ничего общего с тем, где вы объявляете свои переменные в функции.sub rsp, 1008
выделяется место для всего массива за пределами оператора if. Это верно дляclang
иgcc
в каждой версии и оптимизации уровня я попробовал.Я процитирую некоторые утверждения из руководства для gcc версии 4.7.0 для ясного объяснения.
«Компилятор может принимать несколько базовых стандартов, таких как« c90 »или« c ++ 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". Но это не так. Что касается проблемы, что объявлять все переменные вначале лучше или хуже, это просто вопрос привычки.
источник