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

Почему/разрешал 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;
}
4b9b3361

Ответ 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. Вы можете с радостью сравнить целое число с указателем на машине вроде этого.

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