Погодження викликів

Матеріал з Вікіпедії — вільної енциклопедії.
Перейти до: навігація, пошук

В програмуванні, погодження виклику або угода про виклики це схема за якою підпрограми отримують параметри від викликових підпрограм і як вони повертають результат і управління, погодження викликів можуть різнитися:

  • місцем розташування параметрів і значень до повернення (в регістрах; у стеку; в обох)
  • порядком в якому передаються параметри (або частини одного параметра)
  • розподілом завдань з встановлення і очистки після виклику підпрограми між викликальною і викликаною підпрограмами
  • регістри які може прямо використовувати викликана підпрограма також іноді різняться

Різні мови програмування використовують різні погодження викликів і, таким чином, можуть виконуватися на різних платформах. Іноді це призводить до проблем коли спільно використовуються модулі написані із використанням різних мов, або коли викликається операційна система чи API з мови відмінної від тої на якій вона/він написан. Навіть програма, яка використовує одну мову програмування може використати декілька погоджень викилків, або вибраних компілятором з метою оптимізації, або вказаних програмістом.

Архітектури майже завжди мають більш ніж одне можливе погодження викликів. З багатьма регістрами загального призначення та іншими можливостями, можлива кількість погоджень викликів досить велика, хоча деяки архітектури надають можливість використовувати лише одне погодження викликів.

Платформа x86[ред.ред. код]

Архітектура x86 підтримує багато погоджень викиликів. Через малу кількість регістрів, погодження викликів на x86 здебільшого передають аргументи через стек, в той час як значення до повернення (або вказівник на нього) передається в регістрі. Деякі погодження викликів передають в регістрах декілька перших аргументів, що може збільшити видатність для маленьких і листових часто викликаних підпрограм (тобто підпрограм, які не викликають інші підпрограми і, таким чином, не мають бути повторновикористовними).

Приклад виклику:

push eAX            ; передати зміст регістра
push byte[eBP+20]   ; передати деяку змінну в пам'яті (синтаксис FASM/TASM)
push 3              ; передати деяку константу
call calc           ; результат виконання знаходиться в eAX

Типова структура викликаної підпрограми: (деякі або всі (окрім ret) інструкції нижче можуть бути оптимізовані видаленням в простих підпрограмах)

calc:
 push eBP            ; зберегти старий вказівник кадра
 mov eBP,eSP         ; отримати новий вказівник кадра
 sub eSP,localsize   ; зарезервувати місце для локальних змінних
 .
 .                   ; виконати обчислення, залишити результат в AX
 .
 mov eSP,eBP         ; звільнити пам'ять локальних змінних
 pop eBP             ; відновити старий вказівник кадра
 ret paramsize       ; звільнити міце параметрів і повернутись

cdecl[ред.ред. код]

Cdecl- тип викликів, який походить з мови програмування C і використовується за замовчуванням в компіляторах С/С++ на архітектурі x86[1]. Параметри в підпрограму передаються через стек, куди поміщаються у зворотному порядку (справа наліво). 32-бітний результат повертається в регістрі процесора EAX. Чисткою стека займається зовнішня підпрограма.

pascal[ред.ред. код]

Тип викликів, що використовується в основному в мові програмування Pascal. Параметри в підпрограму поміщаються в стек в прямому порядку (зліва направо, тобто протилежно до Cdecl), чисткою стека займається внутрішня підпрограма (та що викликається за допомогою цього виклику). Цей тип викликів був поширений в наступних 16-розрядних інтерфейсах: OS/2 1.x, Microsoft Windows 3.x, і Borland Delphi версії 1.x.

stdcall[ред.ред. код]

stdcall - є стандартним типом виклику для Microsoft Win32 API [2] і Open Watcom C++. Являє собою своєрідний гібрид двох попередніх типів. Викликана підпрограма залишається відповідальною за очищення стека, але параметри поміщаються в стек у зворотному (справа-наліво, як і в cdecl). Регістри EAX, ECX, EDX призначені для використання в межах функції. Значення, що повертається, зберігається в регістрі ЕАХ.

fastcall[ред.ред. код]

fastcall - тип викликів при якому параметри передаються через регістри центрального процесора. Це зазвичай найшвидша модель виклику, особливо якщо всі параметри і проміжні результати вміщаються в регістрах, тоді маніпуляції зі стеком взагалі не потрібні. Але цей тип виклику не стандартизований, різні компілятори реалізують його по різному. Тому він використовується тільки у функціях, які програма не експортує і не імпортує.

Платформа AMD64(x86-64)[ред.ред. код]

Розробники платформи AMD64 значно розширили набір регістрів процесора. Це дозволило використати передачу параметрів в регістрах процесора як новий стандарт для 64-бітної архітектури.

Передача параметрів [3]
  • Перші 4 параметри передаються в регістрах, наступні через стек.
    • 1-й параметр у RCX або XMM0.
    • 2-й параметр у RDX або XMM1.
    • 3-й параметр у R8 або XMM2.
    • 4-й параметр у R9 або XMM3.
  • Ціле число (англ. Integer) (8, 16, 32, 64 бітні, включаючи __m64) передається через регістри RCX, RDX, R8, R9.
  • Число з плаваючою комою (англ. Floating Point) передається в регістрах XMM0-XMM3.
  • Інші типи включаючи структури та __m128 передаються через вказівник у регістрах RCX, RDX, R8, R9.
Повернення результату [4]
  • Для (8, 16, 32, 64 бітних типів включаючи __m64 та вказівники) результат повертається у регістрі RAX.
  • Для усіх інших типів (float, double, __m128) результат повертається у регістрі XMM0.
  • Стан незадіяних бітів у регістрах RAX та XMM0 невизначений.

Приклад передачі параметрів x64:

int func(__m64 arg1, _m128 arg2, struct arg3, float arg4, int arg5);
// arg1 у RCX,
// вказівник на arg2 у RDX,
// вказівник на arg3 у R8,
// arg4 у XMM3
// arg5 через стек
// результат повертається у регістрі RAX

References[ред.ред. код]

  1. http://msdn.microsoft.com/en-us/library/zkwh89ks.aspx
  2. http://msdn.microsoft.com/en-us/library/zxk0tw93.aspx
  3. https://msdn.microsoft.com/ru-ru/library/zthk2dkh.aspx
  4. https://msdn.microsoft.com/en-us/library/7572ztz4.aspx