Процедура может активно задействовать регистры для различных задач. Например:
.code sum proc mov rbx, 5 mov rax, 10 add rax, rbx ret sum endp main proc mov rbx, 125 call sum add rax, rbx ; RAX = 20 ret main endp end
Для примера здесь определена процедура sum, которая устанавливает регистры rbx и rax и складывает их значения, помещая результат в регистр rax.
В функции main помещаем в регистр RBX чило 125, затем вызываем процедуру sum. Далее результат процедуры sum - содержимое регистра RAX складываем с числом из регистра RAX. Однако, поскольку вызов процедуры sum изменил значение регистра RBX, то в после ее вызова в этом регистре будет не число 125, а число 5. Соответственно итоговый результат может отличаться от ожидаемого. В данном случае в регистре RAX будет число 20, а не 140.
Кончено, мы могли бы выбрать разные регистры, но данный пример - упрощение, в реальной процедуре одновременно может быть задействовано для сложных вычислений множество регистров. Кроме того, разрабытываемые процедуры могут использоваться во внешней программе, и при разработке процедуры мы можем не знать, какие именно регистры и как именно будет использовать внешняя программа. Поэтому хорошей, а иногда и необходимой практикой при вызове процедуры является сохранение значений используемых регистров в стек, а при завершении процедуры - их восстановление. Например:
.code sum proc push rbx ; сохраняем в стек регистр rbx mov rbx, 5 mov rax, 10 add rax, rbx pop rbx ; восстанавливаем из стека регистр rbx ret sum endp main proc mov rbx, 125 call sum add rax, rbx ; RAX = 140 ret main endp end
В данном случае в начале процедуры sum сохраняем значение rbx, а при завершении восстанавливаем его. Подобным образом стоит сохранять и восстанавливать все используемые регистры в стек. Регистр RAX в данном случае не сохраняется, так как мы ожидаем, что через него функция main получить результат процедуры sum.
При этом в принципе необяхательно сохранять регистры в вызываемой процедуре, мы могли бы это сделать и в вызывающей процедуре:
.code sum proc mov rbx, 5 mov rax, 10 add rax, rbx ret sum endp main proc mov rbx, 125 push rbx ; сохраняем в стек регистр rbx call sum pop rbx ; восстанавливаем из стека регистр rbx add rax, rbx ret main endp end
Однако сохранение регистров в вызываемой процедуре может быть более предпочтительным, так как функция main может вызывать кучу других процедур и постоянно сохранять/восстанавливать регистры при каждом вызове значительно утяжелит код функции main.
Стоит отметить, что при взаимодействии с внешними API может потребоваться в обязательном порядке сохранять данные некоторых регистров. Так, если мы взаимодействуем с Windows API, то есть общие соглашения, что при вызове функций WinAPI необходимо схоранять в стек, а после вызова восстанавливать регистры RBX, RBP, RDI, RSI, RSP, R12, R13, R14 и R15, а также XMM6 - XMM15.
Все, что касается регистров, также можно отнести и к глобальным переменным, которые используются в процедурах:
.data num word 45 .code sum proc mov num, 15 mov rax, 10 add ax, num ; AX = AX + num = 10 + 15 = 25 ret sum endp main proc call sum add ax, num ; AX = AX + num = 25 + 15 = 40 ret main endp end
В данном случае в функции main мы ожидаем, что переменная num будет равна 45, но вызов процедуры sum меняет ее значение на 45. И подобным образом в вызываемой процедуре переменную можно сохранить в стек:
.data num word 45 .code sum proc push num ; сохраняем значение num в стек mov num, 15 mov rax, 10 add ax, num ; AX = AX + num = 10 + 15 = 25 pop num ; восстанавливаем num из стека ret sum endp main proc call sum add ax, num ; AX = AX + num = 25 + 45 = 70 ret main endp end