Подтвердить что ты не робот

Почему старые спецификации языка C требуют, чтобы функции-локальные переменные были объявлены заранее?

На языке программирования C все языковые версии, с которыми я работал, с принудительной декларацией переменных перед тем, как будут оцениваться любые не декларативные/присваивающие выражения. С++, похоже, отказался от этого требования со всех версий. Я также признаю, что более современная версия C также отказалась от этого требования, но я пока не использую ни один из этих стандартов.

У меня есть вопрос: Какая историческая причина заключалась в том, чтобы препятствовать тому, чтобы язык C свободно декларировался по требованию, а не спереди?

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

  • Предотвращение возникновения непонятной поведенческой ошибки компилятора (например, бесконечные петли синтаксического анализа, массивный всплеск памяти для оценки или некоторые странные угловые случаи с макросами.)
  • Предотвращение нежелательного вывода компилятора. Это может быть что угодно: от выхода символа, путающего процесс отладки и простоты разработки средств отладки, до неожиданных заказов хранения стека.
  • читабельность. Мне также трудно это усвоить, поскольку C, хотя и рассчитан на удобочитаемость по сравнению с другими языками той эпохи, не применял этот тип структуры почти нигде. (Если вы не видите, что прототипирование является аналогичным исполнением, но если я вспомню, что прототипы были добавлены в спецификации "89".)
  • Сложность реализации и практические причины. Это тот, на кого я склонен верить. В качестве инженеров мы должны сделать определенные соображения, чтобы отправить жизнеспособный продукт за определенный промежуток времени. Хотя я дам, что профессиональный ландшафт для компьютерных наук и разработки программного обеспечения резко изменился, бизнес по-прежнему является бизнесом. В конце дня я уверен, что Bell хочет готовый продукт, который можно использовать в среде программирования Unix, чтобы продемонстрировать, что они достигли.

Есть ли у кого-нибудь хорошие источники, поддерживающие любой из вышеперечисленных? Я что-то пропустил? Мы можем спекулировать с рассвета до заката, но я ищу хорошие ссылки.

4b9b3361

Ответ 1

Глядя на раннее (6-е издание Unix, 1975) руководство пользователя от домашней страницы Денниса Ритчи, в этой версии функции-локальные переменные могли объявляется в начале функции:

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

function-statement: {statement-list opt statement-list}

Объявление-список не определен (упущение), но с готовностью можно предположить, что имеет грамматику:

Объявление-список: объявление-объявление-объявление opt.

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

Это, очевидно, упрощает реализацию; в раннем исходном коде компилятора c02.c функция заголовка функции blkhed() должна только суммировать пространство стека, используемое объявлениями auto, в то же время записывая их смещение стека и испускать код, чтобы поднять указатель стека на соответствующую сумму. При выходе функции (через return или отпадая от конца) реализация просто нуждается в восстановлении сохраненного указателя стека.

Тот факт, что K & R считает необходимым заявить, что "объявления переменных (включая инициализации) могут следовать за левой скобкой, которая вводит какой-либо составной оператор, а не только тот, который начинает функцию" - это намек на то, что в этот момент он была относительно недавней особенностью. Это также указывает на то, что объединенный синтаксис синтаксиса объявлений также является недавней функцией, и действительно, в деклараторах руководства 1975 года не могут быть инициализаторы.

В руководстве 1975 года в разделе 11.1 конкретно говорится, что:

C не является блочно-структурированным языком; это вполне можно считать дефектом.

Идентификатор блока и инициализации (K & R), который является дефектом, и смешанные декларации и код (C99) являются логическим продолжением.

Ответ 2

В C89 определения переменных должны находиться в начале блока. (См. Стандарт C для определения блока). Это было, насколько я знаю, для упрощения способа обработки переменных в ассемблере. Например, рассмотрим простую функцию:

void foo()
{
    int i = 5;
    printf("%i\n", i);
}

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

В конце концов, речь шла о простоте реализации, а также эффективности, потому что объявление сразу нескольких переменных, что оно в начале блока позволяет компилятору нажимать на них в стеке, и около ~ 89, что также было оценкой эффективности.

Конечно, этот ответ ужасно упрощен и направлен только на то, чтобы дать краткое представление о том, почему это было сделано так, как это было сделано. Для получения дополнительной информации вы, вероятно, должны прочитать несколько черновиков раннего стандарта C89.

Ответ 3

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

Обратите также внимание на то, что в зарождающемся C (описанном в "Справочном руководстве C" ) было запрещено инициализировать переменные (даже локальные) с неконстантными выражениями.

int a 5;
int b a; /* ERROR in nascent versions of C */

(примечание: в синтаксисе инициализации CRM не было символа =). В общем случае это фактически отрицало основное преимущество объявлений переменных в коде: возможность указать значимое значение времени выполнения в качестве инициализатора. Даже в гораздо более современном C89/90 это ограничение по-прежнему формально применяется к агрегатным инициализаторам (хотя большинство компиляторов его игнорировали)

int a = 5, b = a;
struct { int x, y; } s = { a, b }; /* ERRROR even in C89/90 */ 

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