Какой смысл в прототипировании функций? - PullRequest
5 голосов
/ 29 октября 2010

Я следую руководству по изучению проклятий, и весь код C внутри прототипов функционирует до main(), а затем определяет их позже.В своих уроках C ++ я слышал о прототипировании функций, но никогда не делал этого, и, насколько я знаю, это не имеет большого значения для компиляции кода.Это личный выбор программиста больше всего на свете?Если так, то почему он вообще был включен в C?

Ответы [ 5 ]

12 голосов
/ 30 октября 2010

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

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

Обратите внимание, что в C (в отличие от C ++) функция, объявленная foo_t func(), является , а не так же, как функция, объявленная как foo_t func(void).Последний по прототипу не имеет аргументов.Первый объявляет функцию без прототипа.

3 голосов
/ 29 октября 2010

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

x();
y();
main(){

}

y(){
x();
}

x(){
...
more code ...
maybe even y();
}
1 голос
/ 30 октября 2010

Прототипирование функций - это пережиток давних времен написания компиляторов. Раньше считалось, что компилятору ужасно неэффективно делать несколько проходов по исходному файлу для его компиляции.

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

Поскольку это невозможно определить из контекста, без прототипов функций, компилятору нужно будет делать дополнительный проход по каждому из ваших исходных файлов каждый раз, когда один из них компилируется. Это добавило бы дополнительный коэффициент O (n) для любой компиляции (то есть, если бы компиляция была O (m), теперь это была бы O (m * n)), где n - количество файлов в вашем проекте. В больших проектах, где компиляция уже занимает несколько часов, использование двухпроходного компилятора крайне нежелательно.

Объявление всех ваших функций в будущем позволило бы компилятору составить таблицу функций при сканировании файла и определить, когда он обнаружил идентификатор, ссылающийся на функцию или переменную.

В результате этого компиляторы C (и, соответственно, C ++) могут быть чрезвычайно эффективными при компиляции.

1 голос
/ 30 октября 2010

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

Полезно посмотреть, что функция возвращает / какие параметры.

0 голосов
/ 30 октября 2010

Это позволяет вам иметь ситуацию, в которой, скажем, вы можете иметь класс итератора, определенный в отдельном файле .h, который включает класс родительского контейнера.Поскольку вы включили родительский заголовок в итератор, у вас не может быть такого метода, как, скажем, «getIterator ()», поскольку тип возвращаемого значения должен быть классом итератора, и, следовательно, для этого потребуется включить заголовок итератора внутриродительский заголовок, создающий циклический цикл включений (один включает в себя другой, который включает себя, который снова включает другой, и т. д.).

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

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

...