Thunk
W programowaniu komputerowym thunk jest podprogramem używanym do wstrzykiwania obliczeń do innego podprogramu . Thunks są używane głównie do opóźniania obliczeń do czasu, gdy ich wynik będzie potrzebny, lub do wstawiania operacji na początku lub na końcu innego podprogramu. Mają wiele innych zastosowań w generowaniu kodu kompilatora i programowaniu modułowym .
Termin powstał jako kapryśna nieregularna forma czasownika myśleć . Odnosi się do pierwotnego użycia thunks w ALGOL 60 , co wymagało specjalnej analizy (przemyślenia), aby określić, jaki typ procedury wygenerować.
Tło
We wczesnych latach badań nad kompilatorami eksperymentowano na szeroką skalę z różnymi strategiami oceny . Kluczowym pytaniem było, jak skompilować wywołanie podprogramu, jeśli argumentami mogą być dowolne wyrażenia matematyczne, a nie stałe. Jedno podejście, znane jako „ wywołanie według wartości ”, oblicza wszystkie argumenty przed wywołaniem, a następnie przekazuje wynikowe wartości do podprogramu. W konkurencyjnym podejściu „ wywołaj po imieniu ” podprogram standardowy otrzymuje niewycenione wyrażenie argumentu i musi je ocenić.
Prosta implementacja „wywołania według nazwy” może zastąpić kod wyrażenia argumentu dla każdego pojawienia się odpowiedniego parametru w podprogramie, ale może to spowodować powstanie wielu wersji podprogramu i wielu kopii kodu wyrażenia. Jako ulepszenie kompilator może generować podprogram pomocniczy, zwany thunk , która oblicza wartość argumentu. Adres i środowisko tego podprogramu pomocniczego są następnie przekazywane do pierwotnego podprogramu w miejsce oryginalnego argumentu, gdzie można go wywoływać tyle razy, ile potrzeba. Peter Ingerman po raz pierwszy opisał thunks w odniesieniu do języka programowania ALGOL 60, który obsługuje ocenę wywołania według nazwy.
Aplikacje
Programowanie funkcjonalne
Chociaż branża oprogramowania w dużej mierze ustandaryzowała ocenę call-by-value i call-by-reference , w społeczności programistów funkcjonalnych kontynuowano aktywne badania nad call-by-name . W wyniku tych badań powstała seria leniwych języków programowania ewaluacji, w których standardową strategią ewaluacji jest pewien wariant call-by-name. Kompilatory dla tych języków, takie jak Glasgow Haskell Compiler , w dużej mierze polegały na thunks, z dodatkową funkcją polegającą na tym, że thunks zapisują swój początkowy wynik, aby uniknąć ponownego obliczania go; jest to znane jako zapamiętywanie lub na żądanie .
Funkcjonalne języki programowania umożliwiły również programistom jawne generowanie thunków. Odbywa się to w kodzie źródłowym przez zawijanie wyrażenia argumentu w anonimowej funkcji , która nie ma własnych parametrów. Zapobiega to ocenie wyrażenia, dopóki funkcja odbierająca nie wywoła funkcji anonimowej, osiągając w ten sposób ten sam efekt, co wywołanie według nazwy. Przyjęcie anonimowych funkcji w innych językach programowania sprawiło, że ta możliwość jest szeroko dostępna.
Poniżej znajduje się prosta demonstracja w JavaScript (ES6):
// 'hypot' jest funkcją binarną const hypot = ( x , y ) => Math . sqrt ( x * x + y * y ); // 'thunk' to funkcja, która nie przyjmuje żadnych argumentów i po wywołaniu wykonuje potencjalnie kosztowną // operację (w tym przykładzie oblicza pierwiastek kwadratowy) i/lub powoduje efekt uboczny const thunk = () = > hipoteza ( 3
, 4 ); // thunk może być następnie przekazywany bez oceniania... doSomethingWithThunk ( thunk ); // ...lub oceniony thunk (); // === 5
Programowanie obiektowe
Thunks są przydatne w platformach programowania zorientowanego obiektowo , które pozwalają klasie dziedziczyć wiele interfejsów , co prowadzi do sytuacji, w których ta sama metoda może zostać wywołana za pośrednictwem dowolnego z kilku interfejsów. Poniższy kod ilustruje taką sytuację w C++ .
klasa A { public : virtual int Access () const { return value_ ; } prywatny : int wartość_ ; }; klasa B { public : virtual int Access () const { return value_ ; } prywatny : int wartość_ ; }; klasa C :
publiczne A , publiczne B { publiczne : int Dostęp () const nadpisanie { powrót lepsza_wartość_ ; } prywatne : int lepsza_wartosc_ ; }; int użyj ( B * b ) { powrót b -> Dostęp (); } int main () { // ... B jakiś_b
; użyj ( & some_b ); C trochę_c ; użyj ( & trochę_c ); }
W tym przykładzie kod wygenerowany dla każdej z klas A, B i C będzie zawierał tabelę wysyłania , której można użyć do wywołania programu Access
na obiekcie tego typu za pośrednictwem odwołania tego samego typu. Klasa C będzie miała dodatkową tablicę rozsyłania, używaną do wywołania programu Access
na obiekcie typu C poprzez odwołanie typu B. Wyrażenie b->Access()
użyje własnej tabeli rozsyłania B lub dodatkowej tabeli C, w zależności od typu obiektu b dotyczy. Jeśli odnosi się do obiektu typu C, kompilator musi upewnić się, że Access w języku C
otrzyma plik typu adres instancji dla całego obiektu C, a nie odziedziczonej części B tego obiektu.
Jako bezpośrednie podejście do tego problemu dopasowania wskaźnika, kompilator może uwzględnić przesunięcie liczby całkowitej w każdym wpisie tablicy wysyłkowej. To przesunięcie jest różnicą między adresem odniesienia a adresem wymaganym przez implementację metody. Kod wygenerowany dla każdego wywołania za pośrednictwem tych tabel wysyłkowych musi następnie pobrać przesunięcie i użyć go do dostosowania adresu instancji przed wywołaniem metody.
Opisane rozwiązanie ma problemy podobne do opisanej wcześniej naiwnej implementacji call-by-name: kompilator generuje kilka kopii kodu w celu obliczenia argumentu (adresu instancji), jednocześnie zwiększając rozmiary tabeli wysyłkowej, aby zachować przesunięcia. Alternatywnie, kompilator może wygenerować „thunk” dopasowujący wraz z implementacją programu Access w języku C
, który dostosowuje adres instancji o wymaganą wartość, a następnie wywołuje metodę. Thunk może pojawić się w tabeli wysyłkowej C dla B, eliminując w ten sposób potrzebę samodzielnego dostosowywania adresu przez dzwoniących.
Obliczenia numeryczne wymagające oceny w wielu punktach
Procedury obliczeń, takie jak całkowanie, wymagają obliczenia wyrażenia w wielu punktach. Do tego celu użyto wywołania według nazwy w językach, które nie obsługiwały zamknięć ani parametrów procedur .
Interoperacyjność
Thunks są szeroko stosowane w celu zapewnienia interoperacyjności między modułami oprogramowania, których procedury nie mogą wywoływać się bezpośrednio. Może się to zdarzyć, ponieważ procedury mają różne konwencje wywoływania , działają w różnych trybach procesora lub przestrzeniach adresowych lub przynajmniej jedna działa na maszynie wirtualnej . Kompilator (lub inne narzędzie) może rozwiązać ten problem, generując thunk, który automatyzuje dodatkowe kroki potrzebne do wywołania docelowej procedury, niezależnie od tego, czy jest to przekształcanie argumentów, kopiowanie ich do innej lokalizacji, czy przełączanie trybu procesora. Udany thunk minimalizuje dodatkową pracę, którą musi wykonać dzwoniący w porównaniu do normalnego połączenia.
Znaczna część literatury na temat interoperacyjności dotyczy różnych platform Wintel , w tym MS-DOS , OS/2 , Windows i .NET , oraz przejścia z 16-bitowego na 32-bitowe adresowanie pamięci. W miarę jak klienci migrowali z jednej platformy na drugą, klucze stały się niezbędne do obsługi starszego oprogramowania napisanego dla starszych platform.
Przejście z kodu 32-bitowego na 64-bitowy na x86 również wykorzystuje formę thunkingu (WoW64). Ponieważ jednak przestrzeń adresowa x86-64 jest większa niż przestrzeń dostępna dla kodu 32-bitowego, stary mechanizm „generic thunk” nie mógł być użyty do wywołania kodu 64-bitowego z kodu 32-bitowego. Jedyny przypadek, w którym 32-bitowy kod wywołuje kod 64-bitowy, to przełączenie interfejsów API systemu Windows do wersji 32-bitowej w WoW64.
Nakładki i dynamiczne łączenie
W systemach, które nie mają sprzętu do automatycznej pamięci wirtualnej , thunks mogą zaimplementować ograniczoną formę pamięci wirtualnej, znaną jako nakładki . Za pomocą nakładek programista dzieli kod programu na segmenty, które mogą być ładowane i usuwane niezależnie, oraz identyfikuje punkty wejścia do każdego segmentu. Segment, który łączy się z innym segmentem, musi to robić pośrednio przez tablicę rozgałęzień . Kiedy segment znajduje się w pamięci, jego wpisy w tablicy rozgałęzień przeskakują do segmentu. Kiedy segment jest rozładowywany, jego wpisy są zastępowane przez „przeładuj thunks”, które mogą go ponownie załadować na żądanie.
Podobnie systemy, które dynamicznie łączą ze sobą moduły programu w czasie wykonywania, mogą wykorzystywać łącza danych do łączenia modułów. Każdy moduł może wywoływać inne za pośrednictwem tabeli thunks, którą linker wypełnia, gdy ładuje moduł. W ten sposób moduły mogą wchodzić w interakcje bez wcześniejszej wiedzy o tym, gdzie się znajdują w pamięci.
Zobacz też
Technologie Thunka
- Interfejs trybu chronionego DOS (DPMI)
- Usługi trybu chronionego DOS (DPMS)
- J/bezpośredni
- Microsoft Layer dla Unicode
- Usługi wywoływania platformy
- Win32
- Okna na Windowsie
- WoW64
- libffi
Pojęcia pokrewne
- Funkcja anonimowa
- Przyszłość i obietnice
- Zdalne wywołanie procedury
- Podkładka (informatyka)
- Trampolina (przetwarzanie)
- Redukowalna ekspresja