2010-09-21 3 views
24

Я читал Википедию на/заявления C C++ Prototype и я запутался:Цель C/Прототипы C++

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

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

#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); 
} 

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

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

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

+7

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

+0

Вот почему я определяю функции до их вызова (в любом исходном файле, во всяком случае). Это устраняет необходимость в отдельной декларации, хотя это означает, что мой код читается «назад». –

ответ

16

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

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

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

3

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

26

Две причины:

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

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

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

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

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

+0

В случае, если № 2 не включал или что-то в этом роде, скажите, в каком файле найти реальную функцию? В python вы просто импортируете модуль, а затем ссылаетесь на функцию как module.function, не имеют ли пространства имен, на которые можно ссылаться так? – pythonnewbie

+0

Как решить # 1? Ничто в прототипе не сообщает компилятору, что fac вызывает main и/или что main вызывает fac? – pythonnewbie

+0

В случае № 2, как вы думаете, что включает в себя? Они содержат прототипы! Но если вы пишете свою собственную функцию 'fac', * вы должны предоставить прототип, независимо от того, находится ли он в отдельном включенном файле или в том же исходном файле. В случае № 1 (я полагаю, вы имеете в виду ответ Минагоге здесь), проблема заключается не в том, что с рекурсивной зависимостью что-то не так, что если у вас есть рекурсивная зависимость, нет способа заказать две функции так что компилятор читает определение обоих перед обработкой вызова. –

4

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

+0

Это, наряду с привязкой к уже скомпилированной библиотеке, действительно является одной из причин, по которой требуется декларация.Все остальные - косметические/преференции. – codechimp

1

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

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

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

4

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 строк говорить «привет» в каждом из них. Язык C находит вызов в [1], он предполагает, что print - это функция, которая возвращает int и принимает неизвестное количество аргументов (неизвестно компилятору, известному программисту), компилятор принимает, что звонок правильный и продолжает компиляцию. Поскольку определение функции и вызов совпадают, программа хорошо сформирована.

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

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

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