Как вызываемые функции возвращаются вызывающему объекту после вызова?

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

мой вопрос: как вызываемая функция знает, как вернуться к вызывающему объекту? Есть ли механизм, работающий за кулисами через компилятор?

4 ответов


компилятор подчиняется определенному "соглашению о вызове", определенному как часть ABI, на которую вы нацелены. Это соглашение о вызовах будет включать способ для системы узнать, на какой адрес вернуться. Соглашение о вызовах обычно использует поддержку аппаратного обеспечения для вызовов процедур. Например, в Intel обратный адрес помещается в стек:

...процессор помещает значение EIP register (который содержит смещение инструкции, следующей за CALL инструкция) в стеке (для использования позже в качестве указателя инструкции возврата).

возврат из функции осуществляется через ret инструкция:

... процессор выводит указатель инструкции возврата (смещение) из верхней части стека в EIP зарегистрируйтесь и начните выполнение программы с указателя новой инструкции.

для контраст, на ARM, обратный адрес ставится в регистр ссылок:

на BL и BLX инструкции скопируйте адрес следующей инструкции в lr (r14 регистр ссылке).

возврат обычно выполняется путем выполнения movs pc, lr чтобы скопировать адрес из регистра ссылок обратно в счетчик программ реестр.

ссылки:

  1. Руководство Разработчиков Программного Обеспечения Intel
  2. информационный центр ARM

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

  2. CPU имеет механизм для вызова подпрограммы. Это сохранит текущий адрес выполнения в стеке, а затем перенесет обработку на новый адрес. Когда функция выполнена, она выполняет оператор return, который будет получать адрес вызывающего абонента и возобновлять выполнение там.

Если вернуться адрес уничтожается, потому что стек не правильно очищается uo, или память перезаписывается, тогда вы получаете неопределенное поведение. Конечно, точные детали реализации варьируются в зависимости от используемой платформы.


это стало возможным благодаря стеку (особенно в Intel-подобных системах). Допустим, у нас есть метод caller Это включает, скажем,int что он сохраняет локально.

, когда caller( звонки target( этот int должен быть сохранен. Он помещается в стек вместе с адресом, с которого производится вызов. target( может выполнять свою логику, создавать свои собственные локальные переменные и вызывать другие методы. Его локальные переменные будут помещены в стек вместе с вызовом адрес.

, когда target( заканчивается, стек "развернут". Верхняя часть стека, содержащая target(удаляются локальные переменные.

когда методы рекурсируют слишком далеко, стек может стать слишком большим, и может произойти" переполнение стека".


Это требует сотрудничества между вызываемым и вызывающим абонентом.

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