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 (tutaj MyClass 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ż

Linki zewnętrzne