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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Передавання параметрів
  • Перші 4 параметри передаються в регістрах, наступні через стек.[3]
    • 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.
Повернення результату[3]
  • Для (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

Джерела

[ред. | ред. код]
  1. __cdecl (англ.). {{cite book}}: Проігноровано |website= (довідка)
  2. __stdcall (англ.). {{cite book}}: Проігноровано |website= (довідка)
  3. а б x64 calling convention (англ.). {{cite book}}: Проігноровано |website= (довідка)