Размещение объявления переменной в C


Я долго думал, что в 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: %cn", i, c);
        char *s;
        s = "some string";
        puts(s);
    }
    return 0;
}

не должны декларации c и s вызвать ошибку в режиме C89 / ANSI?

7 106

7 ответов:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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). Если эти виды использования не связаны, создайте новые переменные для каждого из этих несвязанных видов использования.

Я процитирую некоторые утверждения из руководства для gcc версии 4.7.0 для четкого объяснения.

"компилятор может принимать несколько базовых стандартов, таких как ‘c90’ или ‘c++98’, и диалекты GNU этих стандартов, такие как ‘gnu90’ или ‘gnu++98’. Указав базовый стандарт, компилятор будет принимать все программы, следующие этому стандарту и использующие расширения GNU, которые ему не противоречат. Например, ‘ - std=c90 ' отключает некоторые несовместимые функции GCC с ISO C90, такими как ключевые слова asm и typeof, но не с другими расширениями GNU, которые не имеют значения в ISO C90, например, опуская средний термин a ?: выражение."

Я думаю, что ключевым моментом вашего вопроса является то, почему gcc не соответствует C89, даже если используется опция "-std=c89". Я не знаю версию вашего gcc, но я думаю, что большой разницы не будет. Разработчик gcc сказал нам, что опция "- std=c89" просто означает расширения, которые противоречат C89 выключены. Таким образом, это не имеет ничего общего с некоторыми расширениями, которые не имеют смысла в С89. И расширение, которое не ограничивает размещение объявления переменной, принадлежит к расширениям, которые не противоречат C89.

честно говоря, каждый будет думать, что он должен соответствовать C89 полностью на первый взгляд вариант "-std=c89". Но это не так. Что касается проблемы, которая объявляет все переменные в начале лучше или хуже, это просто вопрос привычки.