Почему смешивание объявлений и кода было запрещено до C99? - PullRequest
40 голосов
/ 22 октября 2011

Я недавно стал ассистентом преподавателя университетского курса, который в основном преподает C. Курс стандартизирован на C90, в основном благодаря широкой поддержке компиляторов.Одна из очень запутанных концепций для новичков C с предыдущим опытом Java - правило, согласно которому объявления переменных и код не могут смешиваться внутри блока (составной оператор).

Это ограничение было окончательно снято с C99, но мне интересноКто-нибудь знает, почему это было там в первую очередь?Упрощает ли это анализ переменной области?Позволяет ли он программисту указывать, в каких точках выполнения программы должен расти стек для новых переменных?

Я предполагаю, что разработчики языка не добавили бы такое ограничение, если бы оно вообще не имело цели.

Ответы [ 6 ]

52 голосов
/ 22 октября 2011

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

Поэтому язык C был разработан так, чтобы требовать только очень простого компилятора, который компилируется быстро.Это, в свою очередь, приводит к концепции « однопроходного компилятора »: компилятор считывает исходный файл и переводит все в код ассемблера как можно скорее - обычно во время чтения исходного файла.Например: когда компилятор читает определение глобальной переменной, соответствующий код генерируется немедленно.

Эта черта видна в C до сегодняшнего дня:

  • C требует "предварительных объявлений«всего и всего.Многопроходный компилятор может смотреть вперед и выводить объявления переменных функций в одном и том же файле сам по себе.
  • Это, в свою очередь, делает необходимыми *.h файлы.
  • При компиляции функции,расположение кадра стека должно быть вычислено как можно скорее - иначе компилятору пришлось бы сделать несколько проходов по телу функции.

В настоящее время ни один серьезный компилятор C по-прежнему не является "однопроходным", потому что многиеважные оптимизации не могут быть сделаны в течение одного прохода.Немного больше можно найти в Wikipedia .

Стандартное тело задержалось на некоторое время, чтобы ослабить эту точку «единого прохода» в отношении тела функции.Я предполагаю, что другие вещи были важнее.

8 голосов
/ 22 октября 2011

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

Я предполагаю, что разработчики языка не добавили бы такое ограничение, если бы оно не имело абсолютно никакой цели.

Не думайте, что разработчики языка решили ограничить язык. Часто подобные ограничения возникают случайно и при определенных обстоятельствах.

4 голосов
/ 22 октября 2011

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

int a;
int b;
int c;
...

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

Сравните это с:

int a;
foo();
int b;
bar();
int c;

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

Более того, в качестве стилистической проблемы первый подход поощряет более дисциплинированный способ кодирования (неудивительно, что Паскаль слишком усиливает это), имея возможность видеть все локальные переменные в одном месте и в конечном итоге проверять их вместе в целом.Это обеспечивает более четкое разделение между кодом и данными.

2 голосов
/ 22 октября 2011

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

void f()
{
  int a;
  do_something();
  {
    int b = do_something_else();
  }
}
2 голосов
/ 22 октября 2011

Еще во времена C-юности, когда над этим работал Деннис Ритчи, компьютеры (например, PDP-11) имели очень ограниченную память (например, 64 тыс. Слов), а компилятор должен был быть небольшим, поэтому пришлось оптимизировать очень мало вещи и очень просто. И в то время (я кодировал на C на Sun4 / 110 в эпоху 1986-89 гг.) Объявление переменных register было действительно полезным для компилятора.

Современные компиляторы намного сложнее . Например, последняя версия GCC (4.6) содержит более 5 или 10 миллионов строк исходного кода (в зависимости от того, как вы его измеряете) и выполняет большую часть оптимизаций, которых не было, когда появились первые компиляторы C.

И сегодняшние процессоры также сильно отличаются (нельзя предположить, что современные машины похожи на машины 1980-х годов, но в тысячи раз быстрее и имеют тысячи ОЗУ и дисков). Сегодня иерархия памяти очень важна: процессор больше всего тратит кеш (ожидание данных из оперативной памяти). Но в 1980-х доступ к памяти был почти таким же быстрым (или медленным, по современным стандартам), как выполнение одной машинной инструкции. Сегодня это совершенно неверно: для чтения вашего модуля ОЗУ вашему процессору может потребоваться несколько сотен наносекунд, в то время как для данных в кеше L1 он может выполнять более одной инструкции каждую наносекунду.

Так что не думайте о C как о языке, очень близком к аппаратному: это было верно в 1980-х годах, но ложно сегодня.

1 голос
/ 11 июня 2012

Требование, чтобы объявления переменных появлялись в начале составного оператора, не ухудшило выразительность C89.Все, что можно было бы законно сделать, используя объявление в середине блока, можно было бы сделать так же, добавив открытую скобку перед объявлением и удвоив закрывающую скобку вмещающего блока.Хотя такое требование иногда может иметь загроможденный исходный код с дополнительными открывающими и закрывающими скобками, такие скобки не были бы просто шумом - они отмечали бы начало и конец областей действия переменных.

Рассмотрим следующие два примера кода:

{
  do_something_1();
  {
    int foo;
    foo = something1();
    if (foo) do_something_1(foo);
  }
  {
    int bar;
    bar = something2();
    if (bar) do_something_2(bar);
  }
  {
    int boz;
    boz = something3();
    if (boz) do_something_3(boz);
  }
}

и

{
  do_something_1();

  int foo;
  foo = something1();
  if (foo) do_something_1(foo);

  int bar;
  bar = something2();
  if (bar) do_something_2(bar);

  int boz;
  boz = something3();
  if (boz) do_something_3(boz);
}

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

Кроме того, при обработке более простых случаев, включающих смешанные объявления переменныхне будет трудным (даже компилятор 1970-х годов мог бы сделать это, если бы авторы хотели разрешить такие конструкции), все усложнилось бы, если бы блок, содержащий смешанные объявления, также содержал какие-либо метки goto или case.Создатели C, вероятно, думали, что разрешение смешивания объявлений переменных и других операторов слишком усложнит стандарты, чтобы стоить выгоды.

...