Теперь у этих функций нет скрытых глобальных переменных, и они превосходно подходят для иллюстрации: у них есть аргумент и локальные переменные, и они вызывают друг друга. Возможно, вы захотите освежить память и еще раз посмотреть, как выглядят эти функции в законченном виде, но все их основные свойства, относящиеся к механизму вызова функций, уже перечислены.
expression() компилятор создает структуру, напоминающую показанную на рисунке.
Детали зависят от реализации, но в принципе к ним относится информация о том, что функция должна вернуть управление и некое значение в точку вызова. Такую структуру данных называют
Теперь функция expression() вызывает term(), поэтому компилятор создает активационную запись для вызова функции term().
Обратите внимание на то, что функция term() имеет дополнительную переменную d, которую необходимо хранить в памяти, поэтому при вызове мы резервируем для нее место, даже если в коде она нигде не используется. Все в порядке. Для корректных функций (а именно такие функции мы явно или неявно используем в нашей книге) затраты на создание активизационных записей не зависят от их размера. Локальная переменная d будет инициализирована только в том случае, если будет выполнен раздел case '/'.
Теперь функция term() вызывает функцию primary(), и мы получаем следующую картину.
Все это становится довольно скучным, но теперь функция primary() вызывает функцию expression().
expression() также имеет свою собственную активационную запись, отличающуюся от активационной записи первого вызова функции expression(). Хорошо это или плохо, но мы теперь попадаем в очень запутанную ситуацию, поскольку переменные left и t при двух разных вызовах будут разными. Функция, которая прямо или (как в данном случае) косвенно вызывает себя, называется
Итак, каждый раз, когда мы вызываем функцию expression() управление возвращается функции primary(), стек возвращается в предыдущее состояние.
Когда функция primary() возвращает управление функции term(), стек возвращается в состояние, показанное ниже.
И так далее. Этот стек, который часто называют
Запомните, что детали реализации стека зависят от реализации языка С++, но в принципе соответствуют схеме, описанной выше. Надо ли вам знать, как реализованы вызовы функции? Разумеется, нет; мы и до этого прекрасно обходились, но многие программисты любят использовать термины “активационная запись” и “стек вызовов”, поэтому лучше понимать, о чем они говорят.
8.6. Порядок вычислений
Выполнение программы происходит инструкция за инструкцией в соответствии с правилами языка. Когда поток выполнения достигает определения переменной, происходит ее создание, т.е. в памяти выделяется память для объекта, и этот объект инициализируется. Когда переменная выходит из области видимости, она уничтожается, т.е. объект, на который она ссылалась, удаляется из памяти, и компилятор может использовать ранее занимаемый им участок памяти для других целей. Рассмотрим пример.
string program_name = "silly";
vector
void f()
{
string s; // s — локальная переменная в функции f
while (cin>>s && s!="quit") {
string stripped; // stripped — локальная переменная в цикле
string not_letters;
for (int i=0; i
// видимости инструкции
if (isalpha(s[i]))
stripped += s[i];
else
not_letters += s[i];
v.push_back(stripped);
// ...
}
// ...
}