Coding with Titans

so breaking things happens constantly, but never on purpose

Konwencje wywołań

Sposób wywoływania funkcji w programach pisanych w C/C++, Pascalu, assemblerze (rozumianych szeroko jako języki i kod niezarządzany) odgrywa niebagatelne znaczenie. Aby dwie funkcje - wywołująca i wywoływana - mogły się ze sobą dogadać, muszą wcześniej uzgodnić kilka drobnych acz istotnych szczegółów. Należą do nich:

  • sposób, w jaki przekazywane będą argumenty (np. poprzez stos, rejestry lub mieszając oba)
  • sposób, w jaki argumenty są numerowane (od prawej, czy od lewej strony)
  • sposób udekorowania nazwy funkcji (poprzez dodanie przyrostków, przedrostków lub całkowite wygenerowanie nowej nazwy, gwarantujące unikatowość, gdy funkcja jest wielkorotnie przeładowana)
  • czy występuje stała, czy zmienna liczba argumentów (a tym samym, która ze stron będzie odpowiedzialna za posprzątanie)
  • czy występuje wywołanie funkcji globalnej (statycznej) lub metody klasy (C++).

Mimo iż kombinacji wymienionych czynników jest sporo, stosuje się głównie 3 konwencje: fastcall, stdcall, thiscall, cdecl.

Poniżej krótkie omówienie, ze wskazaniem najważniejszych cech oraz zastosowania.

Konwencja stdcall (__stdcall)

  • argumenty odkładane na stosie od prawej do lewej (od końca)
  • funkcja wywoływana odpowiedzialna jest za usunięcie argumentów ze stosu (jako, że ich rozmiar jest znany)
  • nazwa funkcji zostaje udekorowana - otrzymuje znak podkreślenia jako przedrostek a na końcu po znaku małpy, rozmiar argumentów w bajtach (np. void fun(int arg) -> _fun@4)
  • konwencja ta nadaje się wszędzie tam, gdzie występuje znana liczba argumentów oraz występują restrykcje co do rozmiaru kodu binarnego (który mniejszy jest, gdyż to funkcja wywoływana sprząta stos)

Konwencja cdecl (__cdecl)

  • argumenty odkładane na stosie również od prawej do lewej (od końca)
  • funkcja wywołująca odpowiedzialna jest za usunięcie argumentów ze stosu (bo zna ich rozmiar), a to pozwala na stosowanie zmiennej liczby argumetów (np.: printf)
  • nazwa funkcji zostaje poprzedzona podkreślnikiem (np. void fun(int arg) -> _fun)
  • generuje dłuższy kod binarny od stdcall (jako, że sprzątanie stosu jest wstawiane po każdym wywołaniu), ale pozwala na wywołania funkcji ze zmienną listą argumentów

Ciekawe porównanie znaleźć można na codeguru.com.

Konwencja fastcall (__fastcall)

  • główną zaletą jest fakt, iż argumenty przekazywane są nie przez stos, a rejestry

  • niemniej jednak wymaga to pewnych założeń

    • rejestry te muszą być w chwili wywołania nieużywane (inaczej ich stan będzie odłożony na stos i zyski z takiego przekazywania argumentów zmaleją)
    • używane są tylko niektóre rejestry (i jeśli funcja ma za dużo argumentów lub ich rozmiar jest za dużo, to i tak będą przekazywane przez stos)
    • kolejność użycia rejestrów zależy wyłącznie od użytego kompilatora (i niektóre pozwalają na własne zdefiniowanie tej kolejności)
    • p. Microsoft C++ Compiler używa ECX i EDX, w których umieszcza dwa pierwsze argumenty (bez upakowania jeśli nie są 32-bitowe), a pozostałe odkłada na stos w kolejności od prawej do lewej (od końca)
  • nazwa funkcji jest poprzedzona znakiem małpy i tak samo jak przy stdcall, na końcu dodany jest również znak małpy oraz rozmiar argumentów w bajtach (np. void fun(int a) -> @fun@4).

Konwencja thiscall

  • jest to konwencja stosowana najczęściej przy wywołaniu funkcji danej klasy (w C++)

  • w funkcjach takich zawsze jako pierwszy parametr występuje niejawnie wskaźnik na obiekt klasy (this) wywołujący zadaną metodę

  • funkcja o stałej liczbie argumentów:

    • sama jest odpowiedzialna za posprzątanie stosu
    • przy Microsoft C++ Compiler, wskaźnik na obiekt klasy (this) jest przekazywany przez rejestr ECX
  • funkcja o zmiennej liczbie argumentów:

    • to wywołujący jest odpowiedzialny za posprzątanie stosu
    • w tym miejscu zachowuje się dokładnie jak cdecl - wszystkie argumenty są odkładane i przekazywane przez stos, razem ze wskaźnikiem na obiekt klasy (this)
  • nazwy funkcji nie są zmieniane