Это не совсем верно. Помимо резервирования стека его еще надо выравнивать на 16. Именно отсюда у вас и взялась "+1". Но на деле этой "+1" может и не быть при четном числе аргументов. https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170
MaKsIm Стек это особая область, при выборке ниже рабочего набора ядро расширит стек. Если размер выборки больше машинной страницы, попытка выборки приведет к завершению процесса.
давайте теперь я напишу. Обычно я передаю параметры процедуре (если параметров больше 4) через mov [rsp+20h+(номер_параметра-5)*8],значение_парметра Параметры через MOV можно передавать функции в любой последовательности, а не как через PUSH только в правильной. Но иногда надоедает писать бесконечные mov [rsp+20h+8*N],XX и я использую PUSH XX, не забывая перед вызовом процедуры добавить еще и sub rsp,20h результат всё равно один и тот же
Как тяжело вам живется Спойлер Код (ASM): includelib user32.lib extern __imp_CreateWindowExA: qword extern __imp_GetMessageA: qword extern __imp_TranslateMessage: qword extern __imp_DispatchMessageA: qword extern __imp_PostQuitMessage: qword MSGSizeInDwords equ 10 MSG_message equ 8 MSG_wParam equ 16 .code main proc local message[MSGSizeInDwords]: dword local hMainWnd: qword local arg12: qword local arg11: qword local arg10: qword local arg9: qword local arg8: qword local arg7: qword local arg6: qword local arg5: qword local args4_1[4]: qword xor rcx, rcx ; dwExStyle, lea rdx, editClassName ; lpClassName, lea r8, initialText ; lpWindowName, mov r9, 0C00000h + 40000h + 20000h + 10000h + 100000h + 200000h + 10000000h + 4 ; dwStyle ( WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_VISIBLE | ES_MULTILINE ) mov arg5, 80000000h ; X, mov arg6, 80000000h ; Y, mov arg7, 80000000h ; nWidth, mov arg8, 80000000h ; nHeight, mov arg9, 0 ; hWndParent, mov arg10, 0 ; hMenu, mov arg11, 0 ; hInstance, mov arg12, 0 ; lpParam call __imp_CreateWindowExA mov hMainWnd, rax messageLoop: lea rcx, message xor rdx, rdx xor r8, r8 xor r9, r9 call __imp_GetMessageA or rax, rax jz messageLoopExit cmp message[MSG_message], 112h ; WM_SYSCOMMAND jnz messageLoop1 cmp message[MSG_wParam], 0F060h ; SC_CLOSE jz messageLoopPostQuit messageLoop1: cmp message[MSG_message], 100h ; WM_KEYDOWN jnz messageLoopProceed cmp message[MSG_wParam], 27 ; Esc jnz messageLoopProceed messageLoopPostQuit: xor rcx, rcx call __imp_PostQuitMessage messageLoopProceed: lea rcx, message call __imp_TranslateMessage lea rcx, message call __imp_DispatchMessageA jmp messageLoop messageLoopExit: xor rax, rax ret main endp editClassName db 'EDIT', 0 initialText db 'NOTEPAD-- ''Esc'' to close', 0 end
Dmitry_Milk, например функция принимает 10 а другая 20,то в сумме я должен зарезервировать 30*8 = 240 байт + 8 выравнивание ,мне уже не нужно будет выделять стёк для следующий функции с 20 аргументами
Entropy, зачем 30? 20 Это же не stdcall, где вызываемая функция обязана была сама подчищать стек после вызова. В конвенции x64 после CALL у тебя RSP остается в том же самом положении, в каком был до вызова. Поэтому его можно вообще не трогать в течение всей функции, а просто сразу писать в зарезервированные места в стеке перед вызовом. А если аргументов 4 или меньше - вообще даже писать в него не надо, просто заполнять регистры.
Dmitry_Milk, я хотел сказать,что один раз вычесть из регистра RSP необходимое количество аргументов для всех последующих вызовов функций,что бы перед каждым вызовом функции не вычитать из RSP количество аргументов
Из под компилятора Visual Studio 2022: Код (ASM): ; асм-листинг функции Add_3: ; int __fastcall Add_3(int a, int b, int c, int d, int e, int f, int g, int h, int z) Add_3 proc near x= dword ptr -18h a= dword ptr 8 b= dword ptr 10h c= dword ptr 18h d= dword ptr 20h e= dword ptr 28h f= dword ptr 30h g= dword ptr 38h h= dword ptr 40h z= dword ptr 48h mov [rsp+d], r9d mov [rsp+c], r8d mov [rsp+b], edx mov [rsp+a], ecx sub rsp, 18h mov eax, [rsp+18h+b] mov ecx, [rsp+18h+a] add ecx, eax mov eax, ecx add eax, [rsp+18h+c] add eax, [rsp+18h+d] add eax, [rsp+18h+e] add eax, [rsp+18h+f] add eax, [rsp+18h+g] add eax, [rsp+18h+h] add eax, [rsp+18h+z] mov [rsp+18h+x], eax mov eax, [rsp+18h+x] add rsp, 18h retn Add_3 endp ; main proc ----> Главная(основная) функция ; int __fastcall main(int argc, const char **argv, const char **envp) main proc e= dword ptr -38h f= dword ptr -30h g= dword ptr -28h h= dword ptr -20h z= dword ptr -18h sub rsp, 58h ; --------------------> Выделяем место в стеке mov edx, 2 ; b mov ecx, 1 ; a call Add_1 mov [rsp+58h+f], 6 ; f mov [rsp+58h+e], 5 ; e mov r9d, 4 ; d mov r8d, 3 ; c mov edx, 2 ; b mov ecx, 1 ; a call Add_2 mov [rsp+58h+z], 9 ; z mov [rsp+58h+h], 8 ; h mov [rsp+58h+g], 7 ; g mov [rsp+58h+f], 6 ; f mov [rsp+58h+e], 5 ; e mov r9d, 4 ; d mov r8d, 3 ; c mov edx, 2 ; b mov ecx, 1 ; a call Add_3 xor eax, eax add rsp, 58h ; --------------------> Чистим стек retn main endp
Да, только их не надо суммировать. Надо выделить под максимум аргументов. Там, где аргументов меньше - использовать только самые нижние слоты, а верхние просто не используются в таком вызове. Скажем, тебе надо вызвать две функции, у одной 6 аргументов, у второй 8. Код (ASM): sub rsp, 8 * 8 + 8 ; резервируем место под 8 аргументов и выравнивание ... ; надо вызвать функцию с 6 аргументами mov rcx, аргумент1 mov rdx, аргумент2 mov r8, аргумент3 mov r9, аргумент4 mov [rsp + 4*8], аргумент5 mov [rsp + 5*8], аргумент6, дальше слоты [rsp + 6*8] и [rsp + 7*8] в данном вызове не используются call func_with_6_args ... ; надо вызвать функцию с 8 аргументами mov rcx, аргумент1 mov rdx, аргумент2 mov r8, аргумент3 mov r9, аргумент4 mov [rsp + 4*8], аргумент5 mov [rsp + 5*8], аргумент6 mov [rsp + 6*8], аргумент7 mov [rsp + 7*8], аргумент8 call func_with_8_args ... add rsp, 8 * 8 + 8 ; восстанавливаем стек ret
а некторых книгах их советуют складывать --- Сообщение объединено, 9 фев 2026 в 16:39 --- из книги: "Йо Ван Гуй Программирование на ассемблере x64 От начального уровня до профессионального использования AVX" или я не правильно понял ?
Скорее всего неправильно. Написано же: sub rsp,32+56+8 ; Скрытое пространство + 7 аргументов в стеке + выравнивание. Это значит, что в функции 11 агументов. Но нужно еще смотреть в каком состоянии стек. Если в адресе в конце 8 , то 8-ку прибавлять не нужно. Возврат из функции и будет этой 8-кой. Итого, что имеем? 32 +56+8=96, а 96 делится на 16 без остатка - значит с выравниванием, полный порядок. По-моему, как-то так...
Стоит уточнить контекст этого термина. По сути складывать можно применить как раз к подходу поиска максимума. Все аргументы всех вызовов функций складываются в один набор слотов.
У майков всё через известное место. К примеру зачем нужен _fastcall с аргументами через регистры, если всё-равно на входе в ту-же оконную процедуру приходится сохранять RCX,RDX,R8,R9 в стеке, для чего собственно и резервируются 20h байт? Код (ASM): proc DialogProc hwnd, msg, wparam, lparam mov [hwnd],rcx mov [msg], rdx mov [wparam],r8 mov [lparam],r9 cmp [msg],WM_INITDIALOG je @init cmp [msg],WM_COMMAND je @command cmp [msg],WM_CLOSE je @close jmp @next ;...... endp Кстати с ручным выделением фрейма типа sub rsp,xx по схеме выше имеется известный глюк. Так, если зарезервировать фрейм для 7-ми аргументов sub rsp,7*8 +8, и внутри процедуры вызвать какую-нибудь функу рантайма типа printf() с (!)более чем 7 аргументов, то приложение рухнет, т.к. получим перезапись адреса-возврата. Код (ASM): start: push rbp sub rsp,4*8 invoke MessageBox,0,0,0,0 call Example ;<-------- Ошибка! add rsp,4*8 invoke ExitProcess,0 ;--------------------------- proc Example cinvoke printf,<'%x.%x.%x.%x.%x.%x.',0>,\ rax,rbx,rsi,rdi,rbp,rsp endp В fasm имеется директива frame/endf, которая на автомате выделяет один общий фрейм для всех (заключённых в этот блок) апи. Но и она не спасает, если вызывать свою процедуру указанным выше способом.
Необходимость сохранть регистры в зарезервированных слотах возникает только в том случае, если первые четыре аргумента требуются и после вызова какой-нибудь дочерней функции. Но если все вычисления с ними сделать до вызова первой функции, и после нее они больше не нужны - то вполне себе получается хорошая выгода по быстродействию. А особенно хорошая, если дочерняя функция первыми аргументами принимает те же самые и в том же самом порядке. Что вполне может быть реальным случаем, если текущая функция - какая-то обрабатывающая обертка для дочернего вызова (хотя компилятор с С++ это вообще разрулит скорее всего инлайнами).
Применительно к оконной процедуре (про которую в общем-то и тред) это исключено. Более того, в системе есть куча и других апи, которые пихают аргументы из регистров обратно в стек: Код (Text): 0:000> u CreateServiceA sechost!CreateServiceA: 000007fe`fe1b75e8 4c8bdc mov r11,rsp 000007fe`fe1b75eb 45894b20 mov dword ptr [r11+20h],r9d 000007fe`fe1b75ef 4d894318 mov qword ptr [r11+18h],r8 000007fe`fe1b75f3 49895310 mov qword ptr [r11+10h],rdx 000007fe`fe1b75f7 49894b08 mov qword ptr [r11+8],rcx 000007fe`fe1b75fb 53 push rbx 000007fe`fe1b75fc 56 push rsi 000007fe`fe1b75fd 57 push rdi ;.......
Это еще почему? Что мешает в оконной процедуре проверить, что пришедшее сообщение не является одним из интересующих нашу специфическую логику окна (это вполне возможно без порчи регистров) и сразу вызвать DefWindowProc? Там даже регистры не придется заполнять, потому что набор аргументов тот же самый - RCX/RDX/R8/R9 сразу без изменений уйдут в DefWindowProc (естественно, слоты для них все равно придется зарезервировать, но это всего одна команда SUB SP, которая и так в большинстве случаев уже есть в прологе функции).
Ну если в окне всего одна кнопка и больше нет элементов управления, то ничто не мешает. Однако в реальных программах вызываются сотни апи, и куда тогда сохранять эти регистры?