Назначение прототипов C / C ++ - PullRequest
25 голосов
/ 21 сентября 2010

Я читал википедию по операторам C / C ++ Prototype, и я запутался:

Википедия говорит: «Включая прототип функции, вы сообщаете компилятору, что функция« fac »принимает один целочисленный аргумент ивы разрешаете компилятору перехватывать ошибки такого рода. "

и в качестве примера используете приведенное ниже:

#include <stdio.h>

 /* 
  * If this prototype is provided, the compiler will catch the error 
  * in main(). If it is omitted, then the error will go unnoticed.
  */
 int fac(int n);              /* Prototype */

 int main(void) {             /* Calling function */
     printf("%d\n", fac());   /* ERROR: fac is missing an argument! */
     return 0;
 }

 int fac(int n) {             /* Called function  */
     if (n == 0) 
         return 1;
     else 
         return n * fac(n - 1);
}

Но определение функции вызываемой функции уже включает all информация, которую прототип сообщает компилятору, так почему же компилятор не может вывести эту информацию из определения вызываемой функции, поскольку они содержат идентичных операторов / информационное письмо для буквы?

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

Редактировать: Спасибо, ребята.Я предположил, что компиляторы были многопроходными, я думаю.Я избалован современными языками, такими как Python.Это имеет смысл, так как уже давно нужно несколько клуджей, чтобы делать вещи точно за один проход.Это кажется мне более очевидным.Очевидно, это требует довольно глубоких знаний о том, как компилятор связывает и компилирует.

Ответы [ 6 ]

25 голосов
/ 22 сентября 2010

Две причины:

  1. Компилятор считывает файл сверху вниз.Если fac используется в main, который выше fac, и прототип не существует, компилятор не знает, как проверить, что этот вызов выполняется правильно, так как он еще не достиг определения fac.

  2. Можно разбить программу на C или C ++ на несколько файлов.fac может быть определен в файле, совершенно отличном от файла, который в данный момент обрабатывает компилятор, и поэтому ему необходимо знать, что эта функция где-то существует и как она должна вызываться.

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

В C вы можете опустить прототип, а компилятор позволит вам вызвать функцию с любым числом аргументов (включая ноль) и примет тип возврата int.Но то, что во время компиляции она не кричит на вас, не означает, что программа будет работать правильно, если вы не вызываете функцию правильно.Вот почему полезно создавать прототипы на C: так что компилятор может перепроверить от вашего имени.

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

15 голосов
/ 22 сентября 2010

Прототипы позволяют отделить интерфейс от реализации.

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

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

5 голосов
/ 22 сентября 2010

Наиболее важной причиной для прототипа является разрешение круговых зависимостей. Если «main» может вызывать «fac», и «fac» вызывает «main», то вам понадобится прототип, чтобы решить эту проблему.

4 голосов
/ 22 сентября 2010

C и C ++ - два разных языка, и в данном конкретном случае между ними существует огромная разница.Из содержания вопроса я предполагаю, что вы говорите о C.

#include <stdio.h>
int main() {
   print( 5, "hi" );  // [1]
}
int print( int count, const char* txt ) {
   int i;
   for ( i = 0; i < count; ++i ) 
      printf( "%s\n", txt );
}

Это правильная программа на C, которая делает то, что вы можете ожидать: печатает 5 строк с надписью "hi" в каждой из них.Язык C находит вызов в [1], он предполагает, что print - это функция, которая возвращает int и принимает неизвестное количество аргументов (неизвестно компилятору, известно программисту), компилятор предполагает , что вызов правильный и продолжает компиляцию.Поскольку определение функции и совпадение вызовов, программа хорошо сформирована.

Проблема в том, что, когда компилятор анализирует строку в [1], он не может выполнить какой-либо тип проверки, так как он не знает, что это за функция.Если при написании этой строки мы ошибаемся в порядке аргументов и набираем print( "hi", 5 );, то компилятор все равно примет эту строку, так как не имеет предварительного знания print.Так как вызов неверен, даже если код компилируется, он позже не будет работать.

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

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

3 голосов
/ 21 сентября 2010

Компилятор C обрабатывает исходные файлы сверху вниз.Функции, которые появляются после , их использование не учитывается при разрешении типов аргументов.Итак, в вашем примере, если бы main() было внизу файла, вам бы не понадобился прототип для fac() (поскольку определение fac() уже было бы замечено компилятором при компиляции main()).

1 голос
/ 22 сентября 2010

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

Другой способ взглянуть на это (на этот раз как на человека): предположим, что у вас нет функции, определенной как прототип, или - ее исходный код. Однако вы знаете, что в библиотеке, которую дал вам ваш помощник, есть машинный код, который при запуске возвращает определенное ожидаемое поведение. Как мило. Теперь, как вы узнали бы, что такой вызов функции допустим, если нет прототипа, говорящего ему: «Эй, чувак, поверь мне, есть функция с именем такая-то, которая принимает параметры и что-то возвращает»?

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...