Почему / разрешил C неявные объявления функций и переменных без типов?

Почему для языка имеет смысл разрешать неявные объявления функций и переменных без типов? Я получаю, что C старый, но позволяет пропустить объявления и по умолчанию int() (или же int в случае переменных) мне не кажется таким вменяемым даже тогда.

Итак, почему это было первоначально введено? Было ли это действительно полезным? Это на самом деле (все еще) используется?

Примечание: я понимаю, что современные компиляторы выдают вам предупреждения (в зависимости от того, какие флаги вы передаете им), и вы можете отключить эту функцию. Это не вопрос!


Пример:

int main() {
  static bar = 7; // defaults to "int bar"
  return foo(bar); // defaults to a "int foo()"
}

int foo(int i) {
  return i;
}

5 ответов

Решение

См. "Развитие языка Си" Денниса Ричи: http://cm.bell-labs.com/who/dmr/chist.html

Например,

В отличие от широко распространенного синтаксического изменения, возникшего во время создания B, основное семантическое содержание BCPL - его структура типов и правила оценки выражений - оставались неизменными. Оба языка не имеют типов или, скорее, имеют один тип данных, "слово" или "ячейка", битовую комбинацию фиксированной длины. Память на этих языках состоит из линейного массива таких ячеек, и значение содержимого ячейки зависит от применяемой операции. Оператор +, например, просто добавляет свои операнды, используя целочисленную инструкцию сложения, и другие арифметические операции в равной степени не осознают фактического значения их операндов. Поскольку память является линейным массивом, можно интерпретировать значение в ячейке как индекс в этом массиве, и BCPL предоставляет для этого оператор. На языке оригинала это было записано как rv, а позже!, В то время как B использует унарный *. Таким образом, если p - это ячейка, содержащая индекс (или адрес, или указатель) другой ячейки, * p ссылается на содержимое указанной ячейки, либо в виде значения в выражении, либо в качестве цели назначения,

Это отсутствие типа сохранялось в C до тех пор, пока авторы не начали переносить его на машины с разными длинами слов:

Изменения языка в этот период, особенно в 1977 году, были в основном сфокусированы на соображениях переносимости и безопасности типов в попытке справиться с проблемами, которые мы предвидели и наблюдали при переносе значительного объема кода на новую платформу Interdata. C в то время все еще проявлялись сильные признаки его без типичного происхождения. Например, указатели едва различались от интегральных индексов памяти в ранних руководствах по языку или в существующем коде; Сходство арифметических свойств указателей символов и целых чисел без знака затрудняло сопротивление соблазну их идентифицировать. Типы без знака были добавлены, чтобы сделать арифметику без знака доступной, не путая ее с манипулированием указателем. Точно так же ранний язык потворствовал назначениям между целыми числами и указателями, но эта практика начала обескураживаться; нотация для преобразования типов (называемая `приведение 'на примере Алгола 68) была изобретена для более явного определения преобразований типов. Воодушевленный примером PL/I, ранний C не связывал указатели структуры со структурами, на которые они указывали, и позволял программистам писать указатель-> член почти независимо от типа указателя; такое выражение некритически воспринималось как ссылка на область памяти, обозначенную указателем, в то время как имя элемента указывало только смещение и тип.

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

Это обычная история - истерический изюм (он же "исторические причины").

Вначале на больших компьютерах, на которых работал C (DEC PDP-11), было 64 КиБ для данных и кода (позже по 64 КиБ для каждого). Был предел того, насколько сложным вы могли бы сделать компилятор и при этом запустить его. Действительно, существовал скептицизм в отношении того, что вы можете написать O/S, используя язык высокого уровня, такой как C, вместо того, чтобы использовать ассемблер. Итак, были ограничения по размеру. Кроме того, мы говорим давно, в начале и середине 1970-х годов. Вычислительная система в целом была не такой зрелой дисциплиной, как сейчас (а компиляторы, в частности, были гораздо менее понятны) Кроме того, языки, из которых был получен язык C (B и BCPL), не имели типов. Все это были факторы.

Язык развивался с тех пор (слава богу). Как было подробно отмечено в комментариях и ответах с отрицательным голосом, в строгом C99, неявное int для переменных и неявных объявлений функции были сделаны устаревшими. Однако большинство компиляторов по-прежнему распознают старый синтаксис и разрешают его использование с более или менее предупреждениями для обеспечения обратной совместимости, поэтому старый исходный код продолжает компилироваться и запускаться, как и всегда. C89 во многом стандартизировал язык как есть, бородавки (gets()) и все. Это было необходимо, чтобы сделать стандарт C89 приемлемым.

Все еще существует старый код, использующий старые нотации - я провожу довольно много времени, работая над древней кодовой базой (около 1982 года для самых старых частей), которая до сих пор не была полностью преобразована в прототипы везде (и это меня сильно раздражает, но только один человек может сделать на основе кода несколько миллионов строк кода). Очень мало из этого все еще имеет "неявное intдля переменных; Есть слишком много мест, где функции не объявляются перед использованием, и несколько мест, где возвращаемый тип функции все еще неявно int, Если вам не нужно работать с такими беспорядками, будьте благодарны тем, кто ушел раньше вас.

Вероятно, лучшее объяснение "почему" происходит отсюда:

Среди языков этого класса наиболее характерны две идеи языка C: отношения между массивами и указателями и способ, которым синтаксис объявления имитирует синтаксис выражения. Они также являются одними из наиболее часто критикуемых функций и часто служат камнем преткновения для новичка. В обоих случаях исторические происшествия или ошибки усугубляют их трудности. Самым важным из них была стойкость компиляторов Си к ошибкам в типе. Как должно быть ясно из истории выше, C эволюционировал из языков без типов. Он не внезапно показался своим первым пользователям и разработчикам совершенно новым языком со своими собственными правилами; вместо этого нам постоянно приходилось адаптировать существующие программы по мере развития языка и учитывать существующий объем кода. (Позже комитет ANSI X3J11, стандартизирующий C, столкнется с той же проблемой.)

Языки системного программирования не обязательно нуждаются в типах; ты слоняешься вокруг с байтами и словами, а не с плавающей точкой и целыми числами, структурами и строками. Система типов была привита к ней по частям, а не была частью языка с самого начала. По мере того, как C перешел от языка системного программирования в основном к языку программирования общего назначения, он стал более строгим в отношении обработки типов. Но, хотя парадигмы приходят и уходят, унаследованный код вечен. Там все еще много кода, который опирается на это неявное intи комитет по стандартам не хочет ломать все, что работает. Вот почему потребовалось почти 30 лет, чтобы избавиться от него.

Давным-давно, еще во времена K&R, до появления ANSI, функции выглядели совсем иначе, чем сегодня.

add_numbers(x, y)
{
    return x + y;
}

int ansi_add_numbers(int x, int y); // modern, ANSI C

Когда вы вызываете функцию, как add_numbersСуществует важное различие в соглашениях о вызовах: все типы "продвигаются" при вызове функции. Итак, если вы сделаете это:

// no prototype for add_numbers
short x = 3;
short y = 5;
short z = add_numbers(x, y);

Что происходит x повышен до int, y повышен до intи тип возвращаемого значения int по умолчанию. Точно так же, если вы передадите float это повышено, чтобы удвоиться. Эти правила гарантировали, что прототипы не нужны, если вы получили правильный тип возвращаемого значения и если вы передали правильное число и тип аргументов.

Обратите внимание, что синтаксис для прототипов отличается:

// K&R style function
// number of parameters is UNKNOWN, but fixed
// return type is known (int is default)
add_numbers();

// ANSI style function
// number of parameters is known, types are fixed
// return type is known
int ansi_add_numbers(int x, int y);

В прежние времена обычной практикой было избегать заголовочных файлов по большей части и просто вставлять прототипы прямо в ваш код:

void *malloc();

char *buf = malloc(1024);
if (!buf) abort();

Заголовочные файлы воспринимаются как необходимое зло в C в наши дни, но так же, как современные производные C (Java, C# и т. Д.) Избавились от заголовочных файлов, старожилам тоже не очень нравилось использовать заголовочные файлы.

Тип безопасности

Из того, что я понимаю о старых прежних временах до-C, не всегда было много статической системы печати. Все было int, в том числе указатели. На этом старом языке единственной точкой прототипов функций было бы улавливание ошибок арности.

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

function()
{
    auto x[7];
    x += 1;
}

Цитирование

По бесформенности:

Оба языка [B и BCPL] не имеют типов или, скорее, имеют один тип данных, "слово" или "ячейка", битовую комбинацию фиксированной длины.

Об эквивалентности целых и указателей:

Таким образом, если p является ячейкой, содержащей индекс (или адрес, или указатель) другой ячейки, *p ссылается на содержимое указанной ячейки, либо в виде значения в выражении, либо в качестве цели назначения.

Доказательства теории, что прототипы были опущены из-за ограничений по размеру:

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

Некоторая пища для размышлений. (Это не ответ; мы на самом деле знаем ответ - он разрешен для обратной совместимости.)

И люди должны взглянуть на кодовую базу COBOL или библиотеки f66, прежде чем сказать, почему он не очищен через 30 лет или около того!

gcc по умолчанию не выкладывает никаких предупреждений.

С -Wall а также gcc -std=c99 выкладывай правильную вещь

main.c:2: warning: type defaults to ‘int’ in declaration of ‘bar’
main.c:3: warning: implicit declaration of function ‘foo’

lint функциональность встроена в современную gcc показывает свой цвет.

Интересно, что современный клон lint, безопасный lint - Я имею в виду splint - дает только одно предупреждение по умолчанию.

main.c:3:10: Unrecognized identifier: foo
  Identifier used in code has not been declared. (Use -unrecog to inhibit
  warning)

llvm Компилятор Си clang который также имеет встроенный статический анализатор, как gcc, выплевывает два предупреждения по умолчанию.

main.c:2:10: warning: type specifier missing, defaults to 'int' [-Wimplicit-int]
  static bar = 7; // defaults to "int bar"
  ~~~~~~ ^
main.c:3:10: warning: implicit declaration of function 'foo' is invalid in C99
      [-Wimplicit-function-declaration]
  return foo(bar); // defaults to a "int foo()"
         ^

Раньше люди думали, что нам не нужна обратная совместимость для вещей 80-х. Весь код должен быть очищен или заменен. Но, оказывается, дело не в этом. Большая часть производственного кода остается в доисторические нестандартные времена.

РЕДАКТИРОВАТЬ:

Я не просматривал другие ответы, прежде чем опубликовать свои. Возможно, я неправильно понял намерение автора. Но дело в том, что было время, когда вы вручную компилировали свой код и использовали переключатель, чтобы поместить двоичный шаблон в память. Им не нужна "система типов". И при этом машина PDP, перед которой Ричи и Томпсон позировали так:

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

К & Р

А также посмотрите, как они использовали для загрузки UNIX в этой статье. Это из руководства Unix 7th edition.

http://wolfram.schneider.org/bsd/7thEdManVol2/setup/setup.html

Суть в том, что им не нужно так много программного уровня, управляющего машиной с памятью размером в КБ. СМЕСЬ Кнута имеет 4000 слов. Вам не нужны все эти типы для программирования компьютера MIX. Вы можете с радостью сравнить целое число с указателем в такой машине.

Я думал, почему они это сделали, совершенно очевидно. Поэтому я сосредоточился на том, сколько еще осталось убрать.

Другие вопросы по тегам