Базовый CS с нуля
Параметры и возвращаемые значения
Ты уже умеешь трассировать стек вызовов по мере вызова и возврата функций. Следующий вопрос: как данные попадают в функцию и как результат возвращается обратно?
Функция, которая всегда работает с одними и теми же фиксированными значениями, почти бесполезна. Настоящие функции принимают параметры — значения, предоставленные вызывающей стороной в момент вызова — и производят возвращаемое значение, которое вызывающая сторона может использовать. Эти два механизма — интерфейс между функцией и кодом, который её вызывает, и оба работают через кадр стека.
После этого урока ты сможешь объяснить, что такое параметр (значение, скопированное в новый кадр в момент вызова), что такое возвращаемое значение (значение, произведённое функцией и переданное вызывающей стороне), и трассировать вызов функции с параметрами и возвращаемым значением, показывая содержимое кадра на каждом шаге.
Параметры объявляются в сигнатуре функции. Когда вызывающая сторона выдаёт CALL,
значения, которые она передаёт (называемые аргументами), копируются в новый кадр до
начала выполнения тела функции. Внутри функции параметры выглядят и ведут себя точно как
локальные переменные — это именованные ячейки внутри кадра. Разница в том, что они
заранее заполнены значениями вызывающей стороны, а не устанавливаются присваиванием.
В синтаксисе TypeScript function add(x: number, y: number): number объявляет два
параметра x и y. В точке вызова add(3, 5) значения 3 и 5 — аргументы,
которые копируются в ячейки x и y нового кадра.
Возвращаемые значения работают в обратном направлении. Когда тело функции достигает
оператора return, оно вычисляет возвращаемое значение и делает его доступным для
вызывающей стороны. Аппаратно это обычно делается путём помещения возвращаемого значения
в регистр CPU (быстрый слот хранения внутри самого CPU) до срабатывания RET. Вызывающая
сторона затем читает этот регистр после того, как RET восстанавливает счётчик команд.
Ключевой момент: параметры передают данные в кадр; возвращаемое значение передаёт
данные из кадра. Оба пересечения происходят на границе кадра — в момент CALL (вход)
и RET (выход).
Функция для трассировки: add(x, y) принимает два числа и возвращает их сумму.
Трассировка показывает add(3, 5) с состоянием стека до вызова, во время выполнения
и после возврата.
1
function add(x: number, y: number): number {
2
let result = x + y; // result — локальная переменная в кадре add
3
return result; // передаёт result обратно вызывающей стороне
4
}
5
6
function main(): void {
7
let total = add(3, 5); // вызывающая сторона: аргументы 3 и 5 переданы в add
8
// total теперь хранит 8
9
}
- L1 x и y — параметры: живут в кадре add, заранее заполнены аргументами вызывающей стороны
- L2 result — локальная переменная: кадр add теперь содержит x, y и result
- L3 return: помещает значение result в регистр, затем срабатывает RET
- L7 add(3,5): аргументы 3 и 5 копируются в новый кадр add как x и y
- L8 после RET: регистр с 8 прочитан; total присваивается 8 в кадре main
Трассировка main(), которая вызывает add(3, 5). Каждая ячейка показывает содержимое
одного кадра.
1
function add(x: number, y: number): number {
2
let result = x + y;
3
return result;
4
}
5
6
function main(): void {
7
let total = add(3, 5);
8
}
Почему это работает
Почему параметры копируются в кадр, а не используются совместно с вызывающей стороной?
Потому что каждый вызов функции нуждается в независимом рабочем пространстве. Если бы
add могла изменять 3 и 5 вызывающей стороны, вызвать add дважды с разными
аргументами было бы невозможно осмыслить. Копирование значений означает, что кадр
вызываемой функции полностью самодостаточен — она может свободно изменять ячейки x
и y, не затрагивая ничего в кадре вызывающей стороны. Это поведение «копировать при
входе» называется передачей по значению и является стандартом в большинстве языков
для примитивных значений, таких как числа.
function multiply(a: number, b: number): number { return a * b; } — Сколько параметров у multiply?
Вызывается multiply(4, 6). Какое значение возвращает multiply?
После возврата multiply(4, 6) сколько кадров стека на стеке (при условии, что она была вызвана из main и ничего больше не активно)?
function square(n: number): number { return n * n; } — Вызывается square(7). Какое значение помещается в регистр возврата?
function add(x: number, y: number): number { let r = x + y; return r; } — Кадр add содержит x, y и r. Сколько именованных ячеек содержит кадр add?
При вызове add(3, 5), где живут значения 3 и 5 во время выполнения add?
Параметры — именованные ячейки в кадре стека функции, заранее заполненные
аргументами (значениями), которые вызывающая сторона передала в момент вызова.
Внутри функции параметры ведут себя точно как локальные переменные. Значения
копируются в кадр (передача по значению для примитивов), поэтому у вызываемой
функции есть независимое рабочее пространство. Возвращаемое значение производится
оператором return: значение помещается в регистр CPU, срабатывает RET, кадр
снимается, и вызывающая сторона читает возвращаемое значение из регистра. Параметры
несут данные в кадр при CALL; возвращаемое значение несёт данные из кадра при
RET.