Даже если на первый взгляд кажется странным возвращать результат через стек, в некоторых случаях это единственный путь для возврата результата. Например, если результат не помещается в регистр или не помещается на стек сопроцессора. Вы должны думать об коротких строках, записях и множествах, которые не помещаются в регистр. Но мы не говорим о таких типах данных, которые возвращаются, как указатель на результат и не говорим о результатах, передаваемые как var параметр. В предыдущей главе, мы уже обсуждали эти принципы. Тем не менее, когда размер результата не известен заранее, как для длинных строк до их создания, некоторые типы занимают фиксированное количество байт в памяти, и место для их размещения может быть выделено компилятором еще до вызова функции. Это то, что в действительности применимо для записей, статических массивов и больших множеств, также и для коротких строк.
Допустим, что у нас есть запись TMyRecord, объявленная следующим образом:
type
TMyRecord = record
A: Integer;
B: Double;
C: Integer;
end;
Компилятор знает, что эта запись занимает 16 байт в памяти. Поэтому, мы можем объявить функцию, которая возвращает запись как результат, объявление будет выглядеть так:
function MyFunction(I: Integer): TMyRecord; register;
Как отмечено в главе 1.6, переменная Result передается в функцию как дополнительный var параметр. Поэтому регистр EDX хранит указатель на результат. Но в отличие от примера с AnsiString, память для результата уже выделена компилятором до входа в функцию, если быть точным, то на стеке. Поэтому нам не нужно самостоятельно выделять память в куче. Достаточно заполнить эту память, которую компилятор резервировал для этой цели. Надо быть только острожным и не выйти за пределы отведенной памяти! Если же это произойдет, то будет разрушен стек и как результат - повисание программы или ошибка доступа (access violation).
В связи с тем, что память уже выделена компилятором, и регистр EDX содержит указатель на эту память (в действительности это стековая память), мы можем просто использовать регистр EDX для заполнения результата:
mov [EDX],EAX
Мы заполнили первое двойное слово (член записи A) содержимым регистра EAX. Заметим, что в данном случае мы не заботились о расчете смещения, относительно регистра EDX, для доступа к нужному члену записи. Но вы должны все-таки написать так, чтобы позволить компилятору сделать эту работу за вас:
mov [Result].A,EAX
Это то же самое, что и выше, но компилятор знает, что Result - это указатель, хранящийся в регистре EDX, и вы можете использовать более ясную точечную нотацию для адресации членов записи. Компилятор сам рассчитает смещение. Строго рекомендуется использовать именно точечную нотацию везде, где только возможно.