В C ++ FAQ:
Предполагая типичную реализацию C ++, которая имеет регистры и стек,
регистры и параметры записываются в стек непосредственно перед
вызовите g (), тогда параметры будут считаны из стека внутри g ()
и снова прочитайте, чтобы восстановить регистры, пока g () возвращается к f ().
относительно вызова вложенной функции
void f()
{
int x = /*...*/;
int y = /*...*/;
int z = /*...*/;
...code that uses x, y and z...
g(x, y, z);
...more code that uses x, y and z...
}
1 / Все ли реализации C ++ с регистрами и стеком? Означает ли это: реализация зависит от архитектуры компилятора / процессора / компьютера?
2 / Какова последовательность инструкций (без языка ассемблера, просто большая картинка), когда я звоню f()
? Я читал разные вещи по этой теме, а также я не помню, чтобы регистры упоминались, а только в стеке.
3 / какие дополнительные особенности / моменты следует подчеркнуть, когда вы имеете дело с вложенными функциями?
Спасибо
Для номера 2
это зависит от многих вещей, включая компилятор и платформу. Обычно называются различные способы передачи и возврата аргументов в функции соглашения о вызовах. статья Соглашения о вызовах на платформе x86 вдаваясь в некоторые подробности о последовательности операций, вы можете увидеть, насколько уродливым и сложным он становится с этой небольшой комбинацией платформ и компиляторов, что, скорее всего, является причиной того, что вы слышали все виды различных сценариев, Ген на соглашениях о вызовах функций. охватывает более широкий набор сценариев, включая 64 bit
платформы, но труднее читать. Это становится еще сложнее, потому что gcc
не может на самом деле push
а также pop
стек, но непосредственно манипулировать указателем стека, мы можем увидеть пример этого, хотя и в сборке Вот. Трудно обобщить соглашения о вызовах, если число аргументов достаточно мало, многие соглашения о вызовах могут избежать использования stack
вообще и буду использовать registers
исключительно.
Что касается числа 3
Вложенные функции ничего не меняют, они просто повторяют процедуру снова для следующего вызова функции.
Что касается числа 1
Как указал Шон .Net
компилирует в байтовый код с выполняет все свои операции в стеке. Страница Википедии на Общий промежуточный язык есть хороший пример.
x86-64 ABI
документ Это еще один отличный документ, если вы хотите понять, как работает конкретное соглашение о вызовах. Figure 3.5 and 3.6
аккуратны, так как они дают хороший пример функции с множеством параметров и того, как каждый параметр передается с использованием комбинации general purpose registers
, floating point registers
и stack
, Такая симпатичная диаграмма — редкая находка при просмотре документов, которые охватывают соглашения о вызовах.
1.
Хотя реализации регистра / стека являются наиболее распространенными базовыми реализациями компилятора C ++, ничто не мешает вам использовать другую архитектуру. Например, вы можете написать компилятор для генерации Java-байт-кода или .NET-байт-кода, и в этом случае у вас будет стековый компилятор C ++.
2.
Когда вы вызываете f (), типичный подход:
Вставьте адрес возврата в стек и перейдите к f ()
В ф ():
Выделите место для локалей x, y и z. Обычно это делается в стеке. Взгляни на Эта статья на стеки вызовов.
Когда вы доберетесь до g (x, y, z), компилятор сгенерирует код для помещения значений в стек, получая доступ к их значениям в кадре стека функции f (). Обратите внимание, что C / C ++ изменяет параметры справа налево.
Когда вы дойдете до конца f (), компилятор вставляет return
инструкции. Вершина стека имеет адрес для возврата (он был передан до вызова f ())
3.
Во вложенных функциях нет ничего особенного, так как все соответствует одному базовому шаблону:
Теперь это общий подход. Компиляторы представят свои собственные оптимизации для улучшения производительности. Например, компилятор может сохранить первые два параметра в регистрах (например).
ПРИМЕЧАНИЕ. Хотя передача параметров стеком, безусловно, является наиболее распространенным подходом, существуют и другие. Взгляните на эту статью на зарегистрировать окна если вы заинтересованы в том, чтобы узнать больше.