_cdecl соглашение о вызовах

В статье о соглашении о вызовах _cdecl автор упомянул:

Освободить локальное хранилище Когда функция выделяет локальное временное пространство, она делает это, уменьшая с точки стека количество необходимого пространства, и этот процесс должен быть обращен вспять, чтобы освободить это пространство. Обычно это делается путем добавления к указателю стека того же количества, которое было вычтено ранее, хотя серия инструкций POP может достичь того же результата.

Мой вопрос: могу ли я просто установить ESP на текущее значение EBP вместо "добавления к указателю стека той же суммы, которая была вычтена" или "серии инструкций POP"?

Подобно:

mov esp, ebp

Мне кажется, это лучший способ, потому что, если я позже изменю количество локальных переменных этой функции, мне не придется беспокоиться о дальнейшем увеличении значения.

2 ответа

Решение

На самом деле, это именно то, что leave инструкция делает, и она была введена для поддержки языков высокого уровня. Тем не менее, он используется не очень часто; большинство компиляторов просто делают явные mov esp, ebp; pop ebp последовательность. Смотрите также этот вопрос.

Однако иногда вы можете выполнить оптимизацию "пропустить указатель кадра". Это освобождает EBP для использования в качестве регистра общего назначения, но вы (или компилятор) должны отслеживать изменения ESP в течение всей функции и использовать возможные изменения смещения для адресации локальных переменных или входящих аргументов. Если вы сделаете это, вам придется использовать pops или явное добавление, чтобы восстановить ESP к его первоначальному значению перед возвратом.

Обратите внимание, что вышесказанное касается всей функции (то есть пролог / эпилог); когда вам нужно позвонить конкретному __cdecl Функция в середине функции, вы не можете просто восстановить ESP к значению EBP, потому что это значение действительно только в самом начале функции, прежде чем какое-либо пространство будет выделено для локальных переменных. Здесь есть два подхода:

1) выдвинуть аргументы, восстановить ESP после вызова:

push offset msg
call _printf
pop ecx ; clobbers ECX but shorter than add esp, 4

2) переместить аргументы в зарезервированные слоты стека; Вам не нужно восстанавливать ESP в этом случае:

mov dword ptr [esp+0], offset msg
call _printf
; no need to change ESP

Если вы выберете второй вариант, вам нужно убедиться, что вы не сохраняете локальные переменные в этих слотах. Также такие mov инструкции, как правило, намного длиннее, чем нажимы, поэтому вам может потребоваться учесть это, если размер кода является проблемой.

Технически это было бы злоупотреблением кадрами стека, которые предназначены для выявления ошибок, возникающих из-за дисбаланса стека, но это было бы совершенно законно.

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

Также, если кто-то собирается поддерживать ваш код, он посчитает его очень запутанным.

Другие вопросы по тегам