Размещение объявления переменной в C - PullRequest
115 голосов
/ 14 ноября 2008

Я долго думал, что в 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?

Ответы [ 7 ]

134 голосов
/ 14 ноября 2008

Он успешно компилируется, поскольку GCC допускает его как расширение GNU, даже если он не является частью стандарта C89 или ANSI. Если вы хотите строго придерживаться этих стандартов, вы должны пройти флаг -pedantic.

72 голосов
/ 14 ноября 2008

Для C89 вы должны объявить все свои переменные в начале блока области действия .

Итак, ваше объявление char c действительно, так как оно находится в верхней части блока области действия цикла for. Но объявление char *s должно быть ошибкой.

27 голосов
/ 05 ноября 2010

Группировка объявлений переменных в верхней части блока, вероятно, является наследием из-за ограничений старых, примитивных компиляторов Си. Все современные языки рекомендуют, а иногда и применяют объявление локальных переменных в самый последний момент: где они впервые инициализированы. Потому что это избавляет от риска использования случайного значения по ошибке. Разделение объявления и инициализации также не позволяет вам использовать «const» (или «final»), когда это возможно.

C ++, к сожалению, продолжает придерживаться старого, верхнего способа объявления обратной совместимости с C (одна совместимость с C перетаскивается из многих других ...) Но C ++ пытается отойти от нее:

  • Дизайн ссылок на C ++ даже не допускает такой вершины группировки блоков.
  • Если вы разделяете объявление и инициализацию локального объекта на C ++, то вы платите за дополнительный конструктор бесплатно. Если конструктор без аргументов не существует, то вам снова не разрешается разделять оба!

C99 начинает двигаться C в том же направлении.

Если вы беспокоитесь о том, что не можете найти место, где объявлены локальные переменные, это означает, что у вас гораздо большая проблема: заключающий блок слишком длинный и должен быть разделен.

https://www.securecoding.cert.org/confluence/display/cplusplus/DCL19-CPP.+Initialize+automatic+local+variables+on+declaration

23 голосов
/ 14 ноября 2008

С точки зрения ремонтопригодности, а не синтаксической точки зрения, существует по крайней мере три направления мысли:

  1. Объявите все переменные в начале функции, чтобы они были в одном месте, и вы сможете сразу увидеть полный список.

  2. Объявите все переменные как можно ближе к месту их первого использования, чтобы вы знали , почему каждая необходима.

  3. Объявите все переменные в начале самого внутреннего блока области видимости, чтобы они как можно скорее вышли из области видимости и позволили компилятору оптимизировать память и сообщить вам, если вы случайно использовали их там, где их не было предназначенный.

Я обычно предпочитаю первый вариант, так как я нахожу, что другие часто заставляют меня искать код для объявлений. Определение всех переменных заранее также упрощает их инициализацию и просмотр в отладчике.

Я иногда объявляю переменные в меньшем блоке области видимости, но только по хорошей причине, которой у меня очень мало. Один пример может быть после fork(), чтобы объявить переменные, необходимые только дочернему процессу. Для меня этот визуальный индикатор является полезным напоминанием об их назначении.

6 голосов
/ 16 мая 2011

Как отмечают другие, GCC разрешает в этом отношении (и, возможно, другие компиляторы, в зависимости от аргументов, с которыми они вызваны) даже в режиме «C89», если только вы не используете «педантичную» проверку. Если честно, не так много веских причин, чтобы не заниматься педантичностью; Качественный современный код должен всегда компилироваться без предупреждений (или очень немногие, если вы знаете, что делаете что-то конкретное, что является подозрительным для компилятора как возможная ошибка), поэтому, если вы не можете заставить свой код компилироваться с педантичной установкой, это, вероятно, требует некоторого внимания.

C89 требует, чтобы переменные объявлялись перед любыми другими операторами в каждой области, более поздние стандарты разрешают объявление ближе к использованию (что может быть как более интуитивным, так и более эффективным), особенно одновременное объявление и инициализацию переменной управления циклом в 'for 'петли.

0 голосов
/ 12 июня 2018

Как уже отмечалось, есть две школы мысли об этом.

1) Объявите все наверху функций, потому что это 1987.

2) Объявлять наиболее близким к первому использованию и в наименьшем возможном объеме.

Мой ответ на этот вопрос - ОБА! Позвольте мне объяснить:

Для длинных функций 1) делает рефакторинг очень трудным. Если вы работаете в кодовой базе, где разработчики против идеи подпрограмм, то у вас будет 50 объявлений переменных в начале функции, и некоторые из них могут быть просто «i» для цикла for, который находится на самом нижняя часть функции.

Исходя из этого, я разработал ПТСР на самом верху и попытался сделать вариант 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. Мы можем заметить, что компилятор смог пройти через код, который использует bar, потому что он не использует 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», которое используется для двух для циклов for). Если использование не связано, создайте новые переменные для каждого из этих не связанных применений.

0 голосов
/ 12 августа 2012

Я приведу некоторые утверждения из руководства для 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". Но это не так. Что касается проблемы, которая объявляет все переменные в начале лучше или хуже, это просто вопрос привычки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...