Страницы

Поиск по вопросам

суббота, 7 марта 2020 г.

Объявление и определение функции (порядок размещения)

#cpp #функции


В каждой единице трансляции, объявление функции обязано предшествовать её вызову.
Если это условие выполняется, то порядок размещения определения уже не имеет особого
значения. 

Какой вариант предпочтительней и почему?
(сначала функция объявлена, а затем определена или наоборот)

void someFunction(); // declaration
void someFunction() { cout << "hello" << endl; }

int main()
{
    someFunction(); // function call                
    return 0;
}


или

void someFunction() { cout << "hello" << endl; }
void someFunction(); // declaration

int main()
{
    someFunction(); // function call                
    return 0;
}

    


Ответы

Ответ 1



Какой вариант предпочтительней и почему? (сначала функция объявлена, а затем определена или наоборот) Хотелось бы узнать причину именно такого порядка размещения. Что это? удобство восприятия программы, отдельное положение стандарта языка, код в этом случае быстрее компилируется или есть ещё какая-то причина? Причина та же самая, по которой вообще в язык добавлена возможность объявлять функции без их определения. Представьте что возможности объявить (без определения!) функции нет. И вам надо написать две функции, вызывающие друг друга: void SomeFirstFunction(int n) { cout << n << endl; SomeSecondFunction(n); } void SomeSecondFunction(int n) { if (n > 0) { SomeFirstFunction(n - 1); } } int main() { SomeFirstFunction(5); } Компилятор разбирает файл сверху вниз. Он доходит до строки SomeSecondFunction(n); В этот момент он понятия не имеет, что это за SomeSecondFunction и какие параметры она принимает - она ведь еще не определена. И он падает с ошибкой: In function 'void SomeFirstFunction(int)': error: 'SomeSecondFunction' was not declared in this scope Перестановка местами SomeSecondFunction и SomeFirstFunction, очевидно, не поможет. Поэтому в язык добавлен хак - вы можете сказать компилятору "SomeSecondFunction - это функция вот с такими параметрами, и она будет определена где-то ниже. Или даже не ниже, а вообще в другом файле - вобщем, смело считай это функцией, генерируй код для ее вызова, потом линковщик разберется где эта функция на самом деле лежит". // предупредили компилятор void SomeSecondFunction(int n); void SomeFirstFunction(int n) { cout << n << endl; // компилятор спокойно сгенерировал вызов еще не определенной (но объявленной!) функции SomeSecondFunction(n); } void SomeSecondFunction(int n) { if (n > 0) { SomeFirstFunction(n - 1); } } При этом нет никакого смысла в объявлении SomeFirstFunction после ее определения - компилятор и так знает что это за функция, ему от еще одного объявления уже известного факта лучше не станет. А вот в примерах из вашего вопроса объявление полностью избыточно. Т.е. ни один из этих вариантов не предпочтительней - они оба бессмысленны, т.к. эквивалентны варианту вообще без declaration: void someFunction() { cout << "hello" << endl; } int main() { someFunction(); // function call return 0; }

Ответ 2



Делать "наборот" нет никакого смысла. Потому что определение функции уже включает в себя объявление. Причина, по которой нужно объявлять до ее использования очень проста. Когда компилятор встречает вызов функции, ему нужно коректно передать параметры, а также правильно сформировать их "проталкивание" в стек/регистры. А также и решить, кто будет "подчищать" стек после вызова. Старый си допускал использование функций до их определений/объявлений, но при этом он считал, что все аргументы функции типа int и возвращаемое значение такое же. UPD кратко. Это запрещено стандартом явно 3.2 One definition rule [basic.def.odr] 1 No translation unit shall contain more than one definition of any variable, function, class type, enumeration type, or template. расширено, но сумбурно В теории, все функции можно разместить так, что бы вызываемые функции были выше (раньше) вызываемых. Это конечно требует дополнительных телодвижений (читать: компиляция будет дольше или нужно ручками все будет расставлять), но возможно. Либо добавлять определение к каждой единице, но это также сильно удлинит компиляцию. Плюс возникает риск того, что в разных единицах компиляции будут разные версии одной и той же функции (из за дефайнов, а это уже весело) либо просто будет дублирование кода, который линковщику ещё и убрать нужно. Поэтому это и запрещено стандартом ( Просто выделение объявлений позволяет делать компиляцию в паралель (или даже на разных машинах). Минимизируется наличие дубликатов функций (inline функции могут дублироваться).

Комментариев нет:

Отправить комментарий