Является ли компилятор / интерпретатор хорошей идеей создавать функции во время интерпретации?

Когда я впервые изучал C ++, я заметил, что функции создаются сверху вниз, в отличие от таких языков, как Java, где порядок «объявлений» функций в коде не имеет значения.

Пример C ++:

#include <iostream>
using namespace std;

int main() {
    test();
    return 0;
}

void test() {

}

Но когда вы меняете порядок функций, программа работает нормально.

Было ли это намеренно, когда создавался C ++?


person mid    schedule 01.09.2016    source источник
comment
Да, я думаю, что целью было ускорить компиляцию.   -  person alain    schedule 01.09.2016
comment
Вы можете просто объявить функцию перед ее использованием, вам не нужно ее определять.   -  person SergeyA    schedule 01.09.2016
comment
@SergeyA Я знаю это. Но проблема не в этом.   -  person mid    schedule 01.09.2016
comment
@Midnightas, но в чем проблема?   -  person SergeyA    schedule 01.09.2016
comment
@SergeyA У меня вообще нет проблем, я просто задала вопрос из-за любопытства.   -  person mid    schedule 01.09.2016
comment
Объявление, предшествующее использованию, позволяет разрабатывать однопроходные компиляторы. Вот почему некоторые языки (среди которых C / C ++) допускают форвардные объявления.   -  person Yves Daoust    schedule 01.09.2016
comment
C - это старый язык. Он существовал в те времена, когда действительно было важнее упростить работу на компьютере, чем облегчить задачу программисту.   -  person    schedule 01.09.2016


Ответы (2)


Ни C, ни C ++ не требуют большого количества определений функций по сравнению с их использованием.

C позволяет объявлять функцию перед использованием, но (в C89 / 90) на самом деле этого не требует. Если был сделан вызов функции, которая не была объявлена, компилятор должен был сделать определенные предположения о типе функции (и код был недопустимым, если определение функции не соответствовало этим предположениям).

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

Для членов класса C ++ несколько ослабляет ограничения. Например, это вполне приемлемо:

class Foo { 
    void bar() { baz(); }
    void baz() {}
};

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


  1. Без этого было бы практически невозможно поддерживать некоторые функции C ++, такие как перегрузка функций.
person Jerry Coffin    schedule 01.09.2016
comment
Возможность определять bar внутри класса была добавлена ​​только в C ++ 11 IIRC. - person alain; 01.09.2016
comment
@alain: вам разрешили определять функции внутри определения класса, начиная с C ++ 98 (и задолго до этого, если на то пошло). - person Jerry Coffin; 01.09.2016
comment
О'кей, спасибо. (Почему-то кажется, что сейчас его используют чаще, чем раньше, не знаю почему) - person alain; 01.09.2016

Проблема возникает из-за ряда ограничений:

  • Семантика значений требует, чтобы для компиляции «вызова» вам нужно было знать параметры и размеры возвращаемых типов.
  • эти знания должны быть доступны компилятору во время компиляции той же единицы трансляции, но ...
  • знания, полученные из определений, доступны только во время компоновки, то есть это отдельный шаг, который происходит помимо компиляции.

Еще больше усложняет то, что грамматика C ++ меняет значение в зависимости от того, являются ли символы переменными или типами (так, без этого знания a<b>c даже невозможно узнать, что это означает: выражение, включающее три переменные, или объявление переменной типа, заданного экземпляром шаблона ).

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

Ваш образец - в широком проекте - будет иметь код как

//test.h
double test();

__

//test.cpp
#include "test.h" //required to check decl & def consistence
double test()
{ /*...*/ }

__

//main.cpp
#include "test.h" // required to know about test() parameters and return
int main()
{ 
   double z = test();   //how does "=" translate? we need to know what test returns
}

Этот «проект» будет скомпилирован с использованием независимых шагов:

g++ -c main.cpp //on a program developer machine
g++ -c test.cpp //on a library developer machine
g++ main.o test.o -o yourprogram //on a "package distributor" machine

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

(Обратите внимание, что у функций-членов нет этой проблемы, поскольку все они должны оставаться в одних и тех же скобках класса и, как следствие, могут соответствовать одной и той же единице перевода)

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

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

Вы можете увидеть другое описание этой проблемы здесь

person Emilio Garavaglia    schedule 01.09.2016