Zniekształcenie nazwy
W konstrukcji kompilatora zniekształcanie nazw (zwane także dekorowaniem nazw ) jest techniką stosowaną do rozwiązywania różnych problemów spowodowanych koniecznością rozwiązywania unikalnych nazw podmiotów programistycznych w wielu współczesnych językach programowania .
Zapewnia sposób kodowania dodatkowych informacji w nazwie funkcji , struktury , klasy lub innego typu danych w celu przekazania bardziej semantycznej informacji z kompilatora do konsolidatora .
Potrzeba zniekształcania nazw pojawia się, gdy język pozwala na nazywanie różnych jednostek tym samym identyfikatorem , o ile zajmują one inną przestrzeń nazw (zwykle zdefiniowaną przez moduł, klasę lub jawną dyrektywę przestrzeni nazw ) lub mają różne podpisy (takie jak w funkcji przeciążenie ). Jest to wymagane w tych przypadkach użycia, ponieważ każda sygnatura może wymagać innej, wyspecjalizowanej konwencji wywoływania w kodzie maszynowym.
Każdy kod obiektowy tworzony przez kompilatory jest zwykle łączony z innymi fragmentami kodu obiektowego (wytworzonymi przez ten sam lub inny kompilator) za pomocą programu zwanego linkerem . Konsolidator potrzebuje wielu informacji na temat każdej jednostki programu. Na przykład, aby poprawnie powiązać funkcję, potrzebna jest jej nazwa, liczba argumentów i ich typy itd.
Proste języki programowania z lat 70., takie jak C , rozróżniały podprogramy tylko po nazwie, ignorując inne informacje, w tym typy parametrów i wartości zwracanych. Późniejsze języki programowania, takie jak C++ , zdefiniowały bardziej rygorystyczne wymagania dotyczące procedur, które należy uznać za „równe”, takie jak typy parametrów, typ zwracany i konwencja wywoływania funkcji. Te wymagania umożliwiają przeciążanie metod i wykrywanie niektórych błędów (takich jak używanie różnych definicji funkcji podczas kompilowania różnych plików źródłowych). Te surowsze wymagania musiały działać z istniejącymi narzędziami i konwencjami; dlatego w nazwie symbolu zakodowano dodatkowe wymagania, ponieważ była to jedyna informacja, jaką tradycyjny linker miał o symbolu.
Innym zastosowaniem zniekształcania nazw jest wykrywanie dodatkowych zmian niezwiązanych z sygnaturami, takich jak czystość funkcji lub to, czy może potencjalnie zgłosić wyjątek lub uruchomić wyrzucanie elementów bezużytecznych. Przykładem języka, który to robi, jest D . Są to bardziej uproszczone sprawdzanie błędów. Na przykład funkcje int f();
i int g(int) czysty;
można było skompilować w jeden plik obiektowy, ale wtedy ich podpisy zmieniły się na float f(); int g(int);
i używany do kompilacji innego źródła, które go nazywa. W czasie łączenia linker wykryje brak funkcji f(int)
i zwrócić błąd. Podobnie konsolidator nie będzie w stanie wykryć, że zwracany typ f
jest inny i zwróci błąd. W przeciwnym razie zostałyby użyte niezgodne konwencje wywoływania, co najprawdopodobniej dałoby niewłaściwy wynik lub spowodowałoby awarię programu. Mangling zwykle nie wychwytuje każdego szczegółu procesu wywołującego. Na przykład nie zapobiega to w pełni błędom, takim jak zmiany składowych danych struktury lub klasy. Na przykład struktura S {}; void f(S) {}
można skompilować w jeden plik obiektowy, wówczas definicja S
zmieniona na struct S { int x; };
i używane w kompilacji wywołania f(S())
. W takich przypadkach kompilator zwykle używa innej konwencji wywoływania, ale w obu przypadkach f
zmieni się na tę samą nazwę, więc konsolidator nie wykryje tego problemu, a rezultatem będzie zwykle awaria lub uszkodzenie danych lub pamięci w czas pracy.
Przykłady
C
Chociaż zniekształcanie nazw nie jest generalnie wymagane ani używane przez języki, które nie obsługują przeciążania funkcji , takie jak C i klasyczny Pascal , używają go w niektórych przypadkach, aby dostarczyć dodatkowych informacji o funkcji. Na przykład kompilatory przeznaczone dla platform Microsoft Windows obsługują różne konwencje wywoływania , które określają sposób, w jaki parametry są wysyłane do podprogramów i zwracane są wyniki. Ponieważ różne konwencje wywoływania są ze sobą niekompatybilne, kompilatory mieszają symbole z kodami wyszczególniającymi, która konwencja powinna być użyta do wywołania określonej procedury.
Schemat zniekształcania został ustanowiony przez firmę Microsoft i był nieformalnie stosowany przez inne kompilatory, w tym Digital Mars, Borland i GNU GCC podczas kompilowania kodu dla platform Windows. Schemat dotyczy nawet innych języków, takich jak Pascal , D , Delphi , Fortran i C# . Dzięki temu podprogramy napisane w tych językach mogą wywoływać lub być wywoływane przez istniejące biblioteki systemu Windows przy użyciu konwencji wywoływania innej niż domyślna.
Podczas kompilowania następujących przykładów C:
0
0
0 int _cdecl fa ( int x ) { powrót ; } int _stdcall g ( int y ) { powrót ; } int _fastcall h ( int z ) { powrót ; }
Kompilatory 32-bitowe emitują odpowiednio:
_f _g@4 @h@4
W schematach manglingu stdcall
i fastcall
funkcja jest zakodowana odpowiednio jako _ nazwa @ X
i @ nazwa @ X
, gdzie X to dziesiętna liczba bajtów argumentu (argumentów) na liście parametrów (w tym przekazanych w rejestrów, dla szybkiego połączenia). W przypadku cdecl
nazwa funkcji jest poprzedzona jedynie podkreśleniem.
Konwencja 64-bitowa w systemie Windows (Microsoft C) nie ma wiodącego znaku podkreślenia. Ta różnica może w niektórych rzadkich przypadkach prowadzić do nierozwiązanych problemów zewnętrznych podczas przenoszenia takiego kodu do wersji 64-bitowej. Na przykład kod Fortran może używać „aliasu” do łączenia z metodą C według nazwy w następujący sposób:
PODPROGRAM f () !DEC$ ATRYBUTY C, ALIAS:'_f' :: f END PODPROGRAM
To skompiluje i połączy dobrze poniżej 32 bitów, ale wygeneruje nierozwiązany zewnętrzny _f
poniżej 64 bitów. Jednym z obejść tego problemu jest całkowite zrezygnowanie z „aliasu” (w którym nazwy metod zazwyczaj muszą być pisane wielkimi literami w C i Fortranie). Innym jest użycie opcji BIND:
PODPROGRAM f () BIND ( C , NAZWA = "f" ) END PODPROGRAM
W C większość kompilatorów modyfikuje również statyczne funkcje i zmienne (a w C++ funkcje i zmienne deklarowane jako statyczne lub umieszczane w anonimowej przestrzeni nazw) w jednostkach translacji, stosując te same zasady manipulacji, co ich wersje niestatyczne. Jeśli funkcje o tej samej nazwie (i parametrach dla C++) są również zdefiniowane i używane w różnych jednostkach tłumaczeniowych, również zostaną zmienione na tę samą nazwę, potencjalnie prowadząc do kolizji. Jednak nie będą one równoważne, jeśli zostaną wywołane w odpowiednich jednostkach tłumaczeniowych. Kompilatory zwykle mogą emitować dowolne zniekształcenia dla tych funkcji, ponieważ bezpośredni dostęp do nich z innych jednostek tłumaczeniowych jest nielegalny, więc nigdy nie będą potrzebować łączenia między różnymi kodami obiektowymi (ich łączenie nigdy nie jest potrzebne). Aby zapobiec konfliktom łączenia, kompilatory będą używać standardowego zniekształcania, ale będą używać tak zwanych symboli „lokalnych”. Podczas łączenia wielu takich jednostek translacji może istnieć wiele definicji funkcji o tej samej nazwie, ale wynikowy kod wywoła tylko jedną lub drugą w zależności od tego, z której jednostki translacji pochodzi. Zwykle odbywa się to za pomocą mechanizm relokacji .
C++
C++ są najbardziej rozpowszechnionymi użytkownikami zniekształcania nazw. Pierwsze kompilatory C++ zostały zaimplementowane jako translatory C , który następnie był kompilowany przez kompilator C do kodu wynikowego; z tego powodu nazwy symboli musiały być zgodne z regułami identyfikatorów C. konsolidator systemu generalnie nie obsługiwał symboli C++, a manglowanie było nadal wymagane.
Język C++ nie definiuje standardowego schematu dekoracji, więc każdy kompilator używa własnego. C++ ma również złożone funkcje językowe, takie jak klasy , szablony , przestrzenie nazw i przeciążanie operatorów , które zmieniają znaczenie określonych symboli w zależności od kontekstu lub użycia. Metadane dotyczące tych cech można ujednoznacznić, zniekształcając (dekorując) nazwę symbolu . Ponieważ systemy zniekształcania nazw dla takich funkcji nie są ustandaryzowane w kompilatorach, niewiele linkerów może łączyć kod obiektowy, który został wyprodukowany przez różne kompilatory.
Prosty przykład
Pojedyncza jednostka translacji C++ może definiować dwie funkcje o nazwie f()
:
0
0 int fa () { powrót 1 ; } int fa ( int ) { powrót ; } pustka sol ( ) { int ja = fa (), j = fa ( ); }
Są to odrębne funkcje, które nie mają ze sobą żadnego związku poza nazwą. Kompilator C++ zakoduje zatem informacje o typie w nazwie symbolu, czego wynikiem będzie coś w rodzaju:
0
0 int __f_v () { powrót 1 ; } int __f_i ( int ) { powrót ; } pustka __g_v () { int ja = __f_v (), j = __f_i ( ); }
Mimo że jego nazwa jest unikalna, g()
jest nadal zniekształcona: zniekształcenie nazwy dotyczy wszystkich symboli C++ (tych, które nie znajdują się w bloku extern „C” {}
).
Złożony przykład
Zniekształcone symbole w tym przykładzie, w komentarzach pod odpowiednią nazwą identyfikatora, to symbole utworzone przez kompilatory GNU GCC 3.x, zgodnie z ABI IA-64 (Itanium):
przestrzeń nazw wikipedia { class article { public : std :: string format (); // = _ZN9wikipedia7article6formatEv bool print_to ( std :: ostream & ); // = _ZN9wikipedia7article8print_toERSo class wikilink { public : wikilink ( std :: string const & name );
// = _ZN9wikipedia7artykuł8wikilinkC1ERKSs }; }; }
Wszystkie zniekształcone symbole zaczynają się od _Z
(zwróć uwagę, że identyfikator rozpoczynający się podkreśleniem, po którym następuje wielka litera, jest zastrzeżonym identyfikatorem w C, więc unika się konfliktu z identyfikatorami użytkownika); w przypadku nazw zagnieżdżonych (obejmujących zarówno przestrzenie nazw, jak i klasy), następuje N
, następnie seria par <length, id> (długość jest długością następnego identyfikatora) i na koniec E
. Na przykład wikipedia::article::format
zmieni się na:
_ZN9wikipedia7artykuł6formatE
W przypadku funkcji po tym następują informacje o typie; ponieważ format()
jest funkcją typu void
, jest to po prostu v
; stąd:
_ZN9wikipedia7artykuł6formatEv
Dla print_to używany jest
standardowy typ std::ostream
(który jest typedef dla std::basic_ostream<char, std::char_traits<char> >
), który ma specjalny alias So
; odniesieniem do tego typu jest zatem RSo
, przy czym pełna nazwa funkcji to:
_ZN9wikipedia7article8print_toERSo
Jak różne kompilatory manipulują tymi samymi funkcjami
Nie ma znormalizowanego schematu, według którego nawet trywialne identyfikatory C++ są zniekształcone, w związku z czym różne kompilatory (lub nawet różne wersje tego samego kompilatora lub ten sam kompilator na różnych platformach) zniekształcają symbole publiczne w radykalnie różnych (a przez to całkowicie niekompatybilnych) sposoby. Zastanów się, jak różne kompilatory C++ manipulują tymi samymi funkcjami:
Kompilator |
pustka h(int)
|
pustka h(int, znak)
|
pustka h (pustka)
|
---|---|---|---|
Intel C++ 8.0 dla Linuksa |
_Z1 cześć
|
_Z1hic
|
_Z1hv
|
HP aC++ A.05.55 IA-64 | |||
IAR EWARM C++ | |||
GCC 3. x i nowsze | |||
Clang 1. x i nowsze | |||
GCC 2.9. X |
h__Fi
|
h__Fic
|
h__Fv
|
HP aC++ A.03.45 PA-RISC | |||
Microsoft Visual C++ v6-v10 ( szczegóły zmiany ) |
?h@@YAXH@Z
|
?h@@YAXHD@Z
|
?h@@YAXXZ
|
Cyfrowy Mars C++ | |||
Borland C++ v3.1 |
@h$qi
|
@h$qizc
|
@h$qv
|
OpenVMS C++ v6.5 (tryb ARM) |
H__XI
|
H__XIC
|
H__XV
|
OpenVMS C++ v6.5 (tryb ANSI) |
CXX$__7H__FIC26CDH77
|
CXX$__7H__FV2CB06E8
|
|
OpenVMS C++ X7.1 IA-64 |
CXX$_Z1HI2DSQ26A
|
CXX$_Z1HIC2NP3LI4
|
CXX$_Z1HV0BCA19V
|
SunPro CC |
__1cBh6Fi_v_
|
__1cBh6Fic_v_
|
__1cBh6F_v_
|
Tru64 C++ v6.5 (tryb ARM) |
h__Xi
|
h__Xic
|
h__Xv
|
Tru64 C++ v6.5 (tryb ANSI) |
__7h__Fi
|
__7h__Fic
|
__7h__Fv
|
Watcom C++ 10.6 |
W?h$n(i)v
|
W?h$n(ia)v
|
W?h$n()v
|
Uwagi:
- Kompilator Compaq C++ na OpenVMS VAX i Alpha (ale nie IA-64) oraz Tru64 ma dwa schematy zniekształcania nazw. Oryginalny, przedstandardowy schemat jest znany jako model ARM i jest oparty na zniekształcaniu nazw opisanym w C++ Annotated Reference Manual (ARM). Wraz z pojawieniem się nowych funkcji w standardowym C++, w szczególności szablonów , schemat ARM stawał się coraz bardziej nieodpowiedni — nie mógł zakodować pewnych typów funkcji lub generował identycznie zniekształcone nazwy dla różnych funkcji. Dlatego został zastąpiony nowszym modelem „ANSI”, który obsługiwał wszystkie funkcje szablonów ANSI, ale nie był kompatybilny wstecz.
- Na IA-64 istnieje standardowy Application Binary Interface (ABI) (zobacz linki zewnętrzne ), który definiuje (między innymi) standardowy schemat zniekształcania nazw i który jest używany przez wszystkie kompilatory IA-64. GNU GCC 3. x dodatkowo przyjęło schemat zniekształcania nazw zdefiniowany w tym standardzie do użytku na platformach innych niż Intel.
- Visual Studio i Windows SDK zawierają program
undname
, który drukuje prototyp funkcji w stylu C dla danej zniekształconej nazwy. - W systemie Microsoft Windows kompilator Intel i Clang używają zniekształceń nazw Visual C++ w celu zapewnienia zgodności.
Obsługa symboli C podczas łączenia z C++
Zadanie wspólnego idiomu C++:
#ifdef __cplusplus extern "C" { #endif /* ... */ #ifdef __cplusplus } #endif
jest upewnienie się, że zawarte w nich symbole są „niezniekształcone” - że kompilator emituje plik binarny z niedekorowanymi nazwami, tak jak zrobiłby to kompilator C. Ponieważ definicje języka C nie są zniekształcone, kompilator C++ musi unikać zniekształconych odniesień do tych identyfikatorów.
Na przykład standardowa biblioteka łańcuchów <string.h>
zwykle zawiera coś w rodzaju:
#ifdef __cplusplus extern "C" { #endif void * memset ( void * , int , size_t ); char * strcat ( char * , const char * ); int strcmp ( const char * , const char * ); znak * strcpy ( znak * ,
stały znak * ); #ifdef __cplusplus } #endif
Zatem kod taki jak:
0
0 if ( strcmp ( argv [ 1 ], "-x" ) == ) strcpy ( a , argv [ 2 ]); else memset ( a , , sizeof ( a ));
używa poprawnego, niezniekształconego strcmp
i memset
. Gdyby extern „C”
nie zostało użyte, kompilator (SunPro) C++ wygenerowałby kod równoważny z:
0
0 if ( __1cGstrcmp6Fpkc1_i_ ( argv [ 1 ], "-x" ) == ) __1cGstrcpy6Fpcpkc_0_ ( a , argv [ 2 ]); else __1cGmemset6FpviI_0_ ( a , , sizeof ( a ));
Ponieważ symbole te nie istnieją w bibliotece środowiska wykonawczego C ( np. libc), wystąpiłyby błędy łącza.
Standaryzowane zniekształcanie nazw w C++
Mogłoby się wydawać, że znormalizowane zniekształcanie nazw w języku C++ prowadziłoby do większej interoperacyjności między implementacjami kompilatorów. Jednak taka standaryzacja sama w sobie nie wystarczyłaby do zagwarantowania interoperacyjności kompilatora C++, a nawet mogłaby stworzyć fałszywe wrażenie, że interoperacyjność jest możliwa i bezpieczna, gdy tak nie jest. Zniekształcanie nazw to tylko jeden z wielu interfejsu binarnego aplikacji (ABI), o których musi decydować implementacja C++ i których należy przestrzegać. Inne aspekty ABI, takie jak obsługa wyjątków , układ tabeli wirtualnej , struktura i dopełnienie ramki stosu powodują również niekompatybilność różnych implementacji C++. Ponadto wymaganie szczególnej formy zniekształcenia spowodowałoby problemy w systemach, w których ograniczenia implementacji (np. długość symboli) narzucają konkretny schemat zniekształcenia. Standaryzowane wymagania dotyczące zniekształcania nazw uniemożliwiłyby również implementację, w której zniekształcanie nie byłoby w ogóle wymagane — na przykład linker, który rozumiałby język C++.
standard C++ nie próbuje standaryzować zniekształcania nazw. Wręcz przeciwnie, Annotated C++ Reference Manual (znany również jako ARM , ISBN 0-201-51459-1 , sekcja 7.2.1c) aktywnie zachęca do korzystania z różnych schematów manipulowania, aby zapobiec łączeniu, gdy inne aspekty ABI są niekompatybilne.
Niemniej jednak, jak wyszczególniono w powyższej sekcji, na niektórych platformach pełny C++ ABI został ustandaryzowany, w tym zniekształcenie nazw.
Rzeczywiste skutki zniekształcenia nazw w C++
Ponieważ symbole języka C++ są rutynowo eksportowane z bibliotek DLL i plików współdzielonych obiektów , schemat zniekształcania nazw nie jest wyłącznie wewnętrzną sprawą kompilatora. Różne kompilatory (lub w wielu przypadkach różne wersje tego samego kompilatora) tworzą takie pliki binarne z różnymi schematami dekoracji nazw, co oznacza, że symbole są często nierozwiązane, jeśli kompilatory użyte do stworzenia biblioteki i programu korzystającego z niej wykorzystywały różne schematy. Na przykład, jeśli system z zainstalowanymi wieloma kompilatorami C++ (np. GNU GCC i kompilator dostawcy systemu operacyjnego) chciał zainstalować biblioteki Boost C++ , musiałby być kompilowany wiele razy (raz dla GCC i raz dla kompilatora dostawcy).
Ze względów bezpieczeństwa dobrze jest, aby kompilatory tworzące niekompatybilne kody obiektowe (kody oparte na różnych ABI, dotyczące np. klas i wyjątków) używały różnych schematów zniekształcania nazw. Gwarantuje to, że te niezgodności zostaną wykryte w fazie łączenia, a nie podczas uruchamiania oprogramowania (co może prowadzić do niejasnych błędów i poważnych problemów ze stabilnością).
Z tego powodu dekorowanie nazw jest ważnym aspektem każdego ABI związanego z C++ .
Istnieją przypadki, szczególnie w dużych, złożonych bazach kodu, w których mapowanie zniekształconej nazwy emitowanej w komunikacie o błędzie linkera z powrotem do konkretnego odpowiadającego tokena/nazwy-zmiennej w źródle może być trudne lub niepraktyczne. Ten problem może sprawić, że identyfikacja odpowiednich plików źródłowych będzie bardzo trudna dla inżynierów kompilujących lub testujących, nawet jeśli używany jest tylko jeden kompilator i konsolidator. Demanglery (w tym te w mechanizmach zgłaszania błędów linkera) czasami pomagają, ale sam mechanizm zniekształcania może odrzucić krytyczne informacje ujednoznaczniające.
Demangle przez C++filt
$ c++filt -n _ZNK3MapI10StringName3RefI8GDScriptE10ComparatorIS0_E16DefaultAllocatorE3hasERKS0_ Map<Nazwa ciągu, Ref<GDScript>, Komparator<Nazwa ciągu>, DefaultAllocator>::has(Nazwa ciągu znaków stała&) const
Demangle za pomocą wbudowanego ABI GCC
#include <stdio.h> #include <stdlib.h> #include <cxxabi.h> int main () { const char * mangled_name = "_ZNK3MapI10StringName3RefI8GDScriptE10ComparatorIS0_E16DefaultAllocatorE3hasERKS0_" ; stan int = -1 ; char * demangled_name = abi :: __cxa_demangle ( zniekształcona_nazwa , NULL , NULL
0
, & status ); printf ( "Odkodowany: %s \n " , nazwa_odkodowany ); za darmo ( nazwa_zmodyfikowana ); powrót ; }
Wyjście:
Zdefragmentowany: Mapa , Komparator , DefaultAllocator>::has(StringName const&) const
Jawa
W Javie sygnatura metody lub klasy zawiera jej nazwę i typy argumentów metody oraz wartość zwracaną, jeśli ma to zastosowanie. Format podpisów jest udokumentowany, ponieważ język, kompilator i format pliku .class zostały zaprojektowane razem (i od samego początku miały na uwadze zorientowanie obiektowe i uniwersalną interoperacyjność).
Tworzenie unikalnych nazw dla klas wewnętrznych i anonimowych
Zakres klas anonimowych jest ograniczony do ich klasy nadrzędnej, więc kompilator musi utworzyć „kwalifikowaną” nazwę publiczną dla klasy wewnętrznej , aby uniknąć konfliktu, gdy inne klasy o tej samej nazwie (wewnętrznej lub nie) istnieją w tej samej przestrzeni nazw. Podobnie klasy anonimowe muszą mieć wygenerowane „fałszywe” nazwy publiczne (ponieważ koncepcja klas anonimowych istnieje tylko w kompilatorze, a nie w środowisku wykonawczym). Tak więc kompilacja następującego programu java
klasa publiczna foo { pasek klasy { publiczny int x ; } public void zark () { Obiekt f = nowy obiekt () { public String toString () { return "cześć" ; } }; } }
utworzy trzy pliki .class :
- foo.class , zawierający główną (zewnętrzną) klasę foo
- foo$bar.class , zawierający nazwaną klasę wewnętrzną foo.bar
- foo$1.class , zawierający anonimową klasę wewnętrzną (lokalną dla metody foo.zark )
Wszystkie te nazwy klas są poprawne (ponieważ symbole $ są dozwolone w specyfikacji JVM) i są one „bezpieczne” do wygenerowania przez kompilator, ponieważ definicja języka Java odradza używanie symboli $ w normalnych definicjach klas Java.
Rozpoznawanie nazw w Javie jest jeszcze bardziej skomplikowane w czasie wykonywania, ponieważ w pełni kwalifikowane nazwy klas są unikalne tylko w określonej instancji modułu ładującego klasy . Moduły ładujące klasy są uporządkowane hierarchicznie, a każdy wątek w JVM ma tak zwany moduł ładujący klasy kontekstu, więc w przypadkach, gdy dwie różne instancje modułu ładującego klasy zawierają klasy o tej samej nazwie, system najpierw próbuje załadować klasę za pomocą głównego (lub systemowego) modułu ładującego klasy a następnie przechodzi w dół hierarchii do programu ładującego klasy kontekstu.
Natywny interfejs Java
Natywna obsługa metod Java umożliwia programom języka Java wywoływanie programów napisanych w innym języku (zwykle C lub C++). Istnieją tutaj dwa problemy związane z rozpoznawaniem nazw, z których żaden nie jest realizowany w szczególnie standardowy sposób:
- Tłumaczenie JVM na natywną nazwę - wydaje się to być bardziej stabilne, ponieważ Oracle upublicznia swój schemat.
- Normalne zniekształcanie nazw C++ - patrz wyżej.
Pyton
W Pythonie mangling jest używany do atrybutów klas, których nie chce się używać w podklasach, które są oznaczone jako takie przez nadanie im nazwy z dwoma lub więcej wiodącymi podkreśleniami i nie więcej niż jednym końcowym podkreśleniem. Na przykład __thing
zostanie zniekształcony, podobnie jak ____thing
i __thing_
, ale __thing__
i __thing___
nie. Środowisko wykonawcze Pythona nie ogranicza dostępu do takich atrybutów, zniekształcenie zapobiega kolizjom nazw tylko wtedy, gdy klasa pochodna definiuje atrybut o tej samej nazwie.
W przypadku napotkania zniekształconych atrybutów nazw Python przekształca te nazwy, poprzedzając je pojedynczym podkreśleniem i nazwą otaczającej klasy, na przykład:
>>> class Test : ... def __mangled_name ( self ): ... pass ... def normal_name ( self ): ... pass >>> t = Test () >>> [ attr for attr in dir ( t ) jeśli „nazwa” w attr ]
['_Test__mangled_name', 'normal_name']
Pascala
Seria Borland Turbo Pascal / Delphi
Aby uniknąć zniekształcania nazw w Pascalu, użyj:
eksportuje nazwę mojej funkcji 'myFunc' , nazwę mojego procedury 'myProc' ;
Wolny Pascal
Free Pascal obsługuje przeciążanie funkcji i operatorów, dlatego używa również zniekształcania nazw do obsługi tych funkcji. Z drugiej strony Free Pascal jest w stanie wywoływać symbole zdefiniowane w zewnętrznych modułach utworzonych w innym języku i eksportować własne symbole do wywoływania w innym języku. Więcej informacji znajdziesz w rozdziałach 6.2 i 7.1 Podręcznika programisty Free Pascal .
Fortran
Zniekształcanie nazw jest również konieczne w kompilatorach języka Fortran , pierwotnie dlatego, że w języku nie jest rozróżniana wielkość liter . Dalsze wymagania dotyczące zniekształcania zostały narzucone później w ewolucji języka z powodu dodania modułów i innych funkcji w standardzie Fortran 90. W szczególności zniekształcanie wielkości liter jest częstym problemem, z którym należy się uporać , aby wywołać biblioteki Fortran, takie jak LAPACK , z innych języków, takich jak C.
Ze względu na niewrażliwość na wielkość liter nazwa podprogramu lub funkcji FOO
musi zostać przekonwertowana przez kompilator na ustandaryzowaną wielkość liter i format, tak aby była łączona w ten sam sposób niezależnie od wielkości liter. Różne kompilatory zaimplementowały to na różne sposoby i nie nastąpiła żadna standaryzacja. AIX i HP-UX Fortran konwertują wszystkie identyfikatory na małe litery foo
, podczas gdy kompilatory Cray i Unicos Fortran konwertują identyfikatory na wielkie litery FOO
. GNU g77 _ kompilator konwertuje identyfikatory na małe litery plus podkreślenie foo_
, z tym wyjątkiem, że identyfikatory już zawierające podkreślenie FOO_BAR
mają dołączone dwa podkreślenia foo_bar__
, zgodnie z konwencją ustaloną przez f2c . Wiele innych kompilatorów, w tym kompilatory IRIX firmy SGI , GNU Fortran i kompilator Fortran firmy Intel (z wyjątkiem Microsoft Windows), konwertuje wszystkie identyfikatory na małe litery plus podkreślenie ( foo_
i foo_bar_
odpowiednio). W systemie Microsoft Windows kompilator Intel Fortran domyślnie używa wielkich liter bez podkreślenia.
Identyfikatory w modułach Fortran 90 muszą być dodatkowo zniekształcone, ponieważ ta sama nazwa procedury może występować w różnych modułach. Ponieważ standard Fortran 2003 wymaga, aby nazwy procedur modułów nie kolidowały z innymi symbolami zewnętrznymi, kompilatory zwykle używają nazwy modułu i nazwy procedury, z wyraźnym znacznikiem pomiędzy nimi. Na przykład:
moduł m zawiera funkcję całkowitą pięć () pięć = 5 funkcja końcowa pięć końcowy moduł m
W tym module nazwa funkcji zostanie zniekształcona jako __m_MOD_five
(np. GNU Fortran), m_MP_five_
(np. ifort Intela), m.five_
(np. Oracle sun95) itp. Ponieważ Fortran nie pozwala na przeciążanie nazw procedura, ale zamiast tego używa ogólnych bloków interfejsu i ogólnych procedur związanych z typem, zniekształcone nazwy nie muszą zawierać wskazówek dotyczących argumentów.
Opcja BIND Fortrana 2003 zastępuje wszelkie zmiany nazw dokonane przez kompilator, jak pokazano powyżej .
Rdza
Nazwy funkcji są domyślnie zniekształcone w Rust . Można to jednak wyłączyć za pomocą #[no_mangle]
. Ten atrybut może służyć do eksportowania funkcji do C, C++ lub Objective-C. Dodatkowo, wraz z #[start]
lub atrybutem skrzyni #[no_main]
, pozwala użytkownikowi zdefiniować punkt wejścia do programu w stylu C.
Rust używał wielu wersji schematów zniekształcania symboli, które można wybrać w czasie kompilacji za pomocą opcji -Z symbol-mangling-version
. Zdefiniowane są następujące manglery:
-
dziedzictwo
Zniekształcenie w stylu C++ oparte na Itanium IA-64 C++ ABI. Symbole zaczynają się od_ZN
, a skróty nazw plików służą do ujednoznacznienia. Używany od wersji Rust 1.9. -
v0
Ulepszona wersja starszego schematu ze zmianami dla Rust. Symbole zaczynają się od_R
. Polimorfizm można zakodować. Funkcje nie mają zakodowanych typów zwracanych (Rust nie ma przeciążeń). Nazwy Unicode używają zmodyfikowanego punycode . Kompresja (backreference) używa adresowania opartego na bajtach. Używany od Rusta 1.37.
Przykłady są podane w testach nazw symboli Rusta
.
Cel C
Objective-C istnieją dwie formy metody : metoda klasy („statyczna”) i metoda instancji . Deklaracja metody w Objective-C ma następującą postać:
00 00 + ( typ zwracany ) nazwa : nazwa parametru 1 : parametr 1 ... – ( typ zwracany ) nazwa : nazwa parametru 1 : parametr 1 ...
Metody klas są oznaczone przez +, metody instancji używają -. Typowa deklaracja metody klasy może wtedy wyglądać następująco:
+ ( id ) initWithX: ( int ) liczba andY: ( int ) liczba ; + ( identyfikator ) nowy ;
Z metodami instancji wyglądającymi tak:
- ( identyfikator ) wartość ; - ( id ) setValue: ( id ) nowa_wartość ;
Każda z tych deklaracji metod ma określoną wewnętrzną reprezentację. Podczas kompilacji każda metoda jest nazywana zgodnie z następującym schematem metod klasowych:
0 _c_ Klasa _ nazwa _ nazwa 1 _ ...
a to na przykład metody:
0 _i_ Klasa _ nazwa _ nazwa 1 _ ...
Dwukropki w składni Objective-C są tłumaczone na podkreślenia. Tak więc metoda klasy Objective-C + ( id ) initWithX: ( int ) liczba andY: ( int ) liczba ;
, jeśli przynależność do klasy Point
tłumaczyłaby się jako _c_Point_initWithX_andY_
, a metoda instancji (należąca do tej samej klasy) - ( id ) wartość ;
przełoży się na _i_Point_value
.
Każda z metod klasy jest oznaczona w ten sposób. Jednak wyszukanie metody, na którą klasa może odpowiedzieć, byłoby żmudne, gdyby wszystkie metody były reprezentowane w ten sposób. Każdej z metod przypisany jest unikalny symbol (taki jak liczba całkowita). Taki symbol jest znany jako selektor . W Objective-C można bezpośrednio zarządzać selektorami — w Objective-C mają one określony typ — SEL
.
Podczas kompilacji tworzona jest tabela, która odwzorowuje reprezentację tekstową, taką jak _i_Point_value
, na selektory (którym nadano typ SEL
). Zarządzanie selektorami jest bardziej wydajne niż manipulowanie tekstową reprezentacją metody. Zauważ, że selektor dopasowuje tylko nazwę metody, a nie klasę, do której należy — różne klasy mogą mieć różne implementacje metody o tej samej nazwie. Z tego powodu implementacje metody również otrzymują określony identyfikator, są one znane jako wskaźniki implementacji, a także otrzymują typ IMP
.
Wysyłane wiadomości są kodowane przez kompilator jako wywołania funkcji id objc_msgSend ( id receiver , SEL selektor , ...)
lub jednej z jej kuzynów, gdzie odbiornik
jest odbiorcą wiadomości, a SEL
określa metodę wywołania. Każda klasa ma własną tabelę, która odwzorowuje selektory na ich implementacje — wskaźnik implementacji określa, gdzie w pamięci znajduje się aktualna implementacja metody. Istnieją osobne tabele dla metod klasy i instancji. Poza tym, że są przechowywane w SEL
do tabel przeglądowych IMP
funkcje są zasadniczo anonimowe.
Wartość SEL
dla selektora nie różni się między klasami. Umożliwia to polimorfizm .
Środowisko uruchomieniowe Objective-C przechowuje informacje o typach argumentów i zwracanych metod. Jednak te informacje nie są częścią nazwy metody i mogą się różnić w zależności od klasy.
Ponieważ Objective-C nie obsługuje przestrzeni nazw , nie ma potrzeby zniekształcania nazw klas (które pojawiają się jako symbole w generowanych plikach binarnych).
Szybki
Swift przechowuje metadane dotyczące funkcji (i nie tylko) w zniekształconych symbolach, które się do nich odnoszą. Te metadane obejmują nazwę funkcji, atrybuty, nazwę modułu, typy parametrów, typ zwracany i inne. Na przykład:
Zniekształcona nazwa metody func calculate(x: int) -> int
klasy MyClass
w teście modułu
to _TFC4test7MyClass9calculatefS0_FT1xSi_Si
dla 2014 Swift. Składniki i ich znaczenie są następujące:
-
_T
: Prefiks dla wszystkich symboli Swift. Od tego wszystko się zacznie. -
F
: Funkcja bez curry. -
C
: Funkcja klasy, czyli metoda -
4test
: Nazwa modułu poprzedzona jego długością. -
7MyClass
: Nazwa klasy, do której należy funkcja, poprzedzona jej długością. -
9calculate
: Nazwa funkcji poprzedzona jej długością. -
f
: Atrybut funkcji. W tym przypadku „f”, co oznacza normalną funkcję. -
S0
: Określa typ pierwszego parametru (mianowicie instancji klasy) jako pierwszy w stosie typów (tutajMyClass
nie jest zagnieżdżony i dlatego ma indeks 0). -
_FT
: Rozpoczyna się lista typów dla krotki parametrów funkcji. -
1x
: Zewnętrzna nazwa pierwszego parametru funkcji. -
Si
: Wskazuje wbudowany typ Swift Swift.Int dla pierwszego parametru. -
_Si
: Zwracany typ: ponownie Swift.Int.
Mangling dla wersji od Swift 4.0 jest oficjalnie udokumentowany. Zachowuje pewne podobieństwo do Itanium.
Zobacz też
- Interfejs programowania aplikacji (API)
- Binarny interfejs aplikacji (ABI)
- Konwencja dzwonienia
- Porównanie oprogramowania do wirtualizacji aplikacji (tj. maszyn wirtualnych)
- Interfejs funkcji obcych (FFI)
- Natywny interfejs Java (JNI)
- Wiązanie językowe
- Stropping
- HAUST
Linki zewnętrzne
- Linux Itanium ABI dla C++ , w tym schemat zniekształcania nazw.
- Standardowa specyfikacja Macintosh C/C++ ABI
- c++filt — filtr do rozszyfrowywania zakodowanych symboli C++ dla kompilatorów GNU/Intel
- undname — narzędzie msvc do rozszyfrowywania nazw.
- demangler.com — narzędzie online do demangulacji symboli GCC i MSVC C++
- System wykonawczy Objective-C — z języka programowania Objective-C 1.0 firmy Apple
- Wywoływanie konwencji dla różnych kompilatorów C++ autorstwa Agnera Foga zawiera szczegółowy opis schematów zniekształcania nazw dla różnych kompilatorów C++ x86 i x64 (s. 24–42 w wersji 2011-06-08)
- C++ Name Mangling/Demangling Dość szczegółowe wyjaśnienie schematu zniekształcania nazw kompilatora Visual C++
- PHP UnDecorateSymbolName skrypt php, który rozkłada nazwy funkcji Microsoft Visual C.
- Mieszanie kodu C i C++
- Levine, John R. (2000) [październik 1999]. „Rozdział 5: Zarządzanie symbolami” . Łączniki i ładowarki . Seria Morgana Kaufmanna w inżynierii oprogramowania i programowaniu (1 wyd.). San Francisco, USA: Morgan Kaufmann . ISBN 1-55860-496-0 . OCLC 42413382 . Zarchiwizowane od oryginału w dniu 05.12.2012 . Źródło 2020-01-12 . Kod: [1] [2] Errata: [3]
- Zniekształcenie nazwy wyjaśnione przez Fivos Kefallonitis