Почему/разрешал 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;
}
Ответы
Ответ 1
См. Деннис Ритчи "Развитие языка C": http://cm.bell-labs.com/who/dmr/chist.html
Например,
В отличие от широко распространенного синтаксического изменения, которое произошло во время создание B, основного семантического содержания BCPL - его структуры типа и правила оценки выражения - остались нетронутыми. Оба языка нетверными или, скорее, имеют один тип данных, "слово" или "ячейку", битовая диаграмма фиксированной длины. Память на этих языках состоит из линейная матрица таких ячеек и значение содержимого ячейки зависит от применяемой операции. Оператор +, например, просто добавляет свои операнды, используя инструкцию integer add machine, и другие арифметические операции в равной степени не смысл их операндов. Поскольку память представляет собой линейный массив, это можно интерпретировать значение в ячейке как индекс в этом массиве, и BCPL поставляет оператора для этой цели. В оригинале язык был записан rv, а позже!, тогда как B использует унарный *. Таким образом, если p - ячейка, содержащая индекс (или адрес, или указатель на) другую ячейку, * p относится к содержимому ячейки, либо как значение в выражении, либо как цель назначение.
Эта безличность сохранялась в C до тех пор, пока авторы не начали портировать ее машинам с разными длинами слов:
В течение этого периода, особенно около 1977 года, языковые изменения были в основном сосредоточены на соображениях мобильности и безопасности типов, в попытке справиться с проблемами, которые мы предвидели и наблюдали в переместив значительную часть кода на новую платформу Interdata. Кот это время все еще проявляло сильные признаки его бесприбыльного происхождения. Указатели, например, были едва различимы от интегральной памяти индексы в ранних руководствах по языку или существующий код; сходство арифметические свойства указателей символов и целых чисел без знака затрудняло сопротивление искушению их идентифицировать. Без знака типы были добавлены, чтобы сделать беззначную арифметику доступной без смешивая его с манипуляцией указателем. Аналогичным образом, ранний язык допустимых присвоений между целыми числами и указателями, но эта практика начали обескураживать; обозначение для преобразований типов (называемое `casts 'из примера Algol 68) был изобретен для указания типа конверсий более явно. Начатый на примере PL/I, ранний C не привязывали структурные указатели к структурам, которые они указывали чтобы и разрешенные программисты могли писать указатель- > почти без отношение к типу указателя; такое выражение было принято некритически в качестве ссылки на область памяти, обозначенную указатель, в то время как имя члена указывает только смещение и тип.
Языки программирования развиваются по мере изменения практики программирования. В современной C и современной среде программирования, где многие программисты никогда не писали язык ассемблера, понятие, что ints и указатели взаимозаменяемы, может казаться почти непостижимым и неоправданным.
Ответ 2
Это обычная история - истерические изюм (иначе говоря, "исторические причины" ).
В начале на больших компьютерах, на которых запущен C (DEC PDP-11), было 64 KiB для данных и кода (позже 64 KiB для каждого). Был предел тому, насколько сложным вы могли бы сделать компилятор и все еще его запустить. Действительно, был скептицизм, что вы могли написать O/S, используя язык высокого уровня, такой как C, вместо того, чтобы использовать ассемблер. Таким образом, существуют ограничения по размеру. Кроме того, мы говорим давно, в начале и середине 1970-х годов. Вычисление вообще не было столь же зрелой дисциплиной, как сейчас (и компиляторы конкретно были гораздо менее понятны). Кроме того, языки, из которых был получен C (B и BCPL), были пустыми. Все это были факторы.
С тех пор язык развивается (слава богу). Как было отмечено в комментариях и ответах вниз, в строгом C99 неявный int
для переменных, а декларации о неявной функции оба были устаревшими. Однако большинство компиляторов по-прежнему распознают старый синтаксис и позволяют использовать его с более или менее предупреждениями для сохранения обратной совместимости, поэтому старый исходный код продолжает компилироваться и запускаться, как всегда. C89 в значительной степени стандартизировал язык как есть, бородавки (gets()
) и все. Это было необходимо для обеспечения приемлемости стандарта C89.
Существует старый код, в котором используются старые обозначения: я трачу довольно много времени на древнюю базу кода (около 1982 года для самых старых частей), которая до сих пор не полностью преобразована в прототипы повсюду (и это раздражает меня очень интенсивно, но на базе кода с несколькими миллионами строк кода может работать только один человек). Очень мало у него все еще есть "неявный int
" для переменных; слишком много мест, где функции не объявлены перед использованием, и несколько мест, где возвращаемый тип функции по-прежнему неявно int
. Если вам не нужно работать с такими беспорядками, будьте благодарны тем, кто прошел перед вами.
Ответ 3
Вероятно, лучшее объяснение "почему" происходит от здесь:
Две идеи наиболее характерны для C среди языков своего класса: взаимосвязь между массивами и указателями и способ синтаксиса синтаксиса выражения. Они также относятся к числу наиболее часто критикованных функций и часто служат камнем преткновения для новичков. В обоих случаях исторические происшествия или ошибки усугубляют их трудности. Наиболее важным из них является толерантность компиляторов C к ошибкам в типе. Как должно быть ясно из предыдущей истории, C эволюционировал из безликих языков. Это не стало неожиданностью для самых ранних пользователей и разработчиков как совершенно новый язык со своими собственными правилами; вместо этого нам постоянно приходилось адаптировать существующие программы в соответствии с разработанным языком и принимать во внимание существующий код. (Позже комитет ANSI X3J11, стандартизирующий C, столкнулся бы с той же проблемой.)
Языки системного программирования необязательно нуждаются в типах; вы обманываете байтами и словами, а не плавает и ints, а также строками и строками. Система типа была привита на нее в виде кусков, а не была частью языка с самого начала. Поскольку C перешел от того, что в основном язык системного программирования на язык программирования общего назначения, он стал более строгим в том, как он обрабатывает типы. Но, хотя парадигмы приходят и уходят, устаревший код навсегда. Там все еще много кода, который полагается на неявный int
, и комитет по стандартам неохотно нарушает все, что работает. Вот почему потребовалось почти 30 лет, чтобы избавиться от него.
Ответ 4
Долгое время назад, в 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, С# и т.д.) избавились от файлов заголовков, старым таймерам тоже не нравилось использование файлов заголовков.
Тип безопасности
Из того, что я понимаю о старых старых днях pre-C, не всегда было много статической системы набора текста. Все было int
, включая указатели. На этом старом языке единственной точкой прототипов функций было бы уловить ошибки arity.
Итак, если мы предположим, что функции были добавлены сначала на язык, а затем система статического типа была добавлена позже, эта теория объясняет, почему прототипы являются необязательными. Эта теория также объясняет, почему массивы распадаются на указатели при использовании в качестве аргументов функции, поскольку в этом прото-C массивы были не более чем указателями, которые автоматически инициализируются, чтобы указать на некоторое пространство в стеке. Например, возможно что-то вроде следующего:
function()
{
auto x[7];
x += 1;
}
Цитирование
О беспарельности:
Оба языка [B и BCPL] являются беспричинными или, скорее, имеют один тип данных, "слово" или "ячейку" - битовый шаблон фиксированной длины.
Об эквивалентности целых чисел и указателей:
Таким образом, если p
- ячейка, содержащая индекс (или адрес или указатель на) другой ячейки, *p
относится к содержимому указанной ячейки, либо как значение в выражении, либо как цель назначения.
Доказательство теории о том, что прототипы были опущены из-за ограничений по размеру:
Во время разработки он постоянно боролся с ограничениями памяти: каждое добавление языка завышало компилятор, поэтому он едва мог поместиться, но каждый переписывающий использование функции уменьшил ее размер.
Ответ 5
Некоторая пища для размышлений. (Это не ответ, мы действительно знаем ответ - он разрешен для обратной совместимости.)
И люди должны смотреть на базу кода 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
C 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 вещей. Весь код должен быть очищен или заменен. Но, оказывается, это не так. Многие производственные коды остаются в доисторических нестандартных раз.
EDIT:
Я не просматривал другие ответы, прежде чем публиковать мои сообщения. Возможно, я неправильно понял намерение плаката. Но дело в том, что было время, когда вы откомпилировали свой код, и используйте toggle, чтобы поместить двоичный паттерн в память. Им не нужна "система типов". Кроме того, машина PDP, перед которой Ричи и Томпсон выглядели так:
Не смотрите на бороду, посмотрите на "переключатели", которые, как я слышал, использовались для загрузки машины.
![K&R]()
А также посмотрите, как они загружали UNIX в этой статье. Это из руководства по выпуску Unix 7-го издания.
http://wolfram.schneider.org/bsd/7thEdManVol2/setup/setup.html
Дело в том, что им не нужно столько программного слоя, управляющего машиной с размером памяти в формате KB. Knuth MIX имеет 4000 слов. Вам не нужны все эти типы для программирования компьютера MIX. Вы можете с радостью сравнить целое число с указателем на машине вроде этого.
Я думал, что , почему они сделали это, совершенно очевидно. Поэтому я сосредоточился на , сколько осталось очистить.