При создании компилятора изменение имени (также называемое украшением имени ) - это метод, используемый для решения различных проблем, вызванных необходимостью разрешать уникальные имена для программных объектов во многих современных языках программирования .
Он предоставляет способ кодирования дополнительной информации в имени функции , структуры , класса или другого типа данных , чтобы передать больше семантической информации от компилятора к компоновщику .
Необходимость в изменении имен возникает, когда язык позволяет различным объектам именоваться одним и тем же идентификатором, если они занимают другое пространство имен (обычно определяемое модулем, классом или явной директивой пространства имен ) или имеют разные подписи (например, в функции перегрузка ). Это требуется в этих случаях использования, поскольку для каждой подписи может потребоваться отдельное специализированное соглашение о вызовах в машинном коде.
Любой объектный код, созданный компиляторами, обычно связан с другими частями объектного кода (созданными тем же или другим компилятором) с помощью программы, называемой компоновщиком . Компоновщику требуется много информации о каждом программном объекте. Например, чтобы правильно связать функцию, ей нужно ее имя, количество аргументов и их типы и так далее.
Простые языки программирования 1970-х годов, такие как C , различали фрагменты кода только по их имени, игнорируя любую другую информацию, такую как типы параметров или возвращаемые типы. Более поздние языки программирования, такие как C ++ , определили более строгие требования к фрагментам кода, которые должны считаться «равными», такие как типы параметров, тип возвращаемого значения и соглашение о вызовах функции. Эти требования позволяют использовать перегрузку функций, а также обнаруживать различные ошибки (например, использовать разные определения функции для компиляции разных исходных файлов). Эти более строгие требования требовали работы с существующими инструментами и соглашениями, поэтому дополнительные требования были закодированы в имени символа, поскольку это была единственная информация, которую традиционный компоновщик имел о символе.
Другое использование искажения имен - это обнаружение дополнительных изменений, не связанных с сигнатурой, таких как чистота функции или возможность потенциально генерировать исключение или запускать сборку мусора. Пример языка делает это D . [1] [2] Это скорее упрощенная проверка ошибок. Например, функции int f();
и int g(int) pure;
могут быть скомпилированы в один объектный файл, но затем их сигнатуры изменились float f(); int g(int);
и использовались для компиляции вызывающего его другого источника. Во время компоновки компоновщик обнаружит, что функция отсутствует, f(int)
и вернет ошибку. Точно так же компоновщик не сможет определить, что тип возвращаемого значения f
отличается, и вернуть ошибку. В противном случае будут использоваться несовместимые соглашения о вызовах, что, скорее всего, приведет к неправильному результату или приведет к сбою программы. Искажение обычно не отражает всех деталей вызывающего процесса. Например, он не полностью предотвращает такие ошибки, как изменение элементов данных структуры или класса. Например, struct S {}; void f(S) {}
может быть скомпилирован в один объектный файл, а затем определение для него будет S
изменено и будет struct S { int x; };
использоваться при компиляции вызова f(S())
. В таких случаях компилятор обычно будет использовать другое соглашение о вызовах, но в обоих случаях f
будет искажать одно и то же имя, поэтому компоновщик не обнаружит эту проблему, и конечным результатом обычно будет сбой или повреждение данных или памяти на время выполнения.
Примеры
C
Хотя изменение имен обычно не требуется и не используется языками, которые не поддерживают перегрузку функций , такими как C и классический Паскаль , они используют его в некоторых случаях для предоставления дополнительной информации о функции. Например, компиляторы, ориентированные на платформы Microsoft Windows, поддерживают множество соглашений о вызовах , которые определяют способ отправки параметров подпрограммам и возврата результатов. Поскольку различные соглашения о вызовах несовместимы друг с другом, компиляторы искажают символы кодами, детализирующими, какое соглашение следует использовать для вызова конкретной подпрограммы.
Схема изменения была разработана Microsoft и неофициально использовалась другими компиляторами, включая Digital Mars, Borland и GNU GCC при компиляции кода для платформ Windows. Схема применима даже к другим языкам, таким как Pascal , D , Delphi , Fortran и C # . Это позволяет подпрограммам, написанным на этих языках, вызывать или вызывать существующие библиотеки Windows, используя соглашение о вызовах, отличное от принятого по умолчанию.
При компиляции следующих примеров на C:
int _cdecl f ( int x ) { вернуть 0 ; } int _stdcall g ( int y ) { возврат 0 ; } int _fastcall h ( int z ) { возврат 0 ; }
32-битные компиляторы выдают соответственно:
_f_g @ 4@ h @ 4
В stdcall
и fastcall
коверкание схемах, функция кодируется , как и соответственно, где Х представляет собой число байтов, в десятичной системе , из аргумента (ов) в списке параметров ( в том числе и передаются в регистрах, на азЬсаИ). В случае с именем функции просто ставится знак подчеркивания._name@X
@name@X
cdecl
64-битное соглашение в Windows (Microsoft C) не имеет символа подчеркивания в начале. Это различие может в некоторых редких случаях привести к нерешенным внешним факторам при переносе такого кода на 64-битную версию. Например, код Fortran может использовать псевдоним для ссылки на метод C по имени следующим образом:
ПОДПРОГРАММА f () ! DEC $ АТРИБУТЫ C, НИКНЕЙМЫ: '_ f' :: f КОНЕЦ ПОДПРОГРАММЫ
Это будет компилировать и связывать нормально под 32 битами, но генерирует неразрешенный внешний код _f
под 64 битами. Один из способов решения этой проблемы - вообще не использовать «псевдоним» (в котором имена методов обычно должны начинаться с заглавной буквы на языках C и Fortran). Другой вариант - использовать опцию BIND:
ПОДПРОГРАММА f () BIND ( C , NAME = "f" ) КОНЕЦ ПОДПРОГРАММЫ
В C большинство компиляторов также изменяют статические функции и переменные (а в C ++ функции и переменные, объявленные статическими или помещенные в анонимное пространство имен) в единицах перевода, используя те же правила изменения, что и для их нестатических версий. Если функции с тем же именем (и параметры для C ++) также определены и используются в разных единицах перевода, это также приведет к искажению одного и того же имени, что может привести к конфликту. Однако они не будут эквивалентными, если вызываются в соответствующих единицах перевода. Компиляторы обычно могут произвольно изменять эти функции, потому что прямой доступ к ним из других единиц трансляции является незаконным, поэтому им никогда не потребуется связывание между различными объектными кодами (связывание их никогда не требуется). Чтобы предотвратить конфликты компоновки, компиляторы будут использовать стандартное искажение, но будут использовать так называемые «локальные» символы. При связывании многих таких единиц перевода может быть несколько определений функции с одним и тем же именем, но результирующий код будет вызывать только одну или другую в зависимости от того, из какой единицы перевода она пришла. Обычно это делается с помощью механизма перемещения .
C ++
Компиляторы C ++ являются наиболее распространенными пользователями искажения имен. Первые компиляторы C ++ были реализованы как трансляторы исходного кода C , который затем компилировался компилятором C в объектный код; из-за этого имена символов должны были соответствовать правилам идентификаторов C. Даже позже, с появлением компиляторов, которые производили машинный код или сборку напрямую, компоновщик системы, как правило, не поддерживал символы C ++, и корректировка все еще требовалась.
Язык C ++ не определяет стандартной схемы оформления, поэтому каждый компилятор использует свою собственную. C ++ также имеет сложные языковые функции, такие как классы , шаблоны , пространства имен и перегрузка операторов , которые изменяют значение определенных символов в зависимости от контекста или использования. Мета-данные об этих функциях могут быть устранены путем изменения (украшения) имени символа . Поскольку системы изменения имен для таких функций не стандартизированы для компиляторов, немногие компоновщики могут связывать объектный код, созданный разными компиляторами.
Простой пример
Одна единица трансляции C ++ может определять две функции с именами f()
:
int f () { возврат 1 ; } int f ( int ) { возврат 0 ; } void g () { int i = f (), j = f ( 0 ); }
Это разные функции, не связанные друг с другом, кроме названия. Таким образом, компилятор C ++ закодирует информацию о типе в имени символа, и результат будет примерно таким:
int __f_v () { возврат 1 ; } int __f_i ( int ) { возврат 0 ; } void __g_v () { int я = __f_v (), j = __f_i ( 0 ); }
Несмотря на то, что его имя уникально, g()
оно все еще искажено: изменение имени применяется ко всем символам C ++ (не в блоке).extern "C"{}
Сложный пример
Искаженные символы в этом примере в комментариях под соответствующим именем идентификатора созданы компиляторами GNU GCC 3.x в соответствии с ABI IA-64 (Itanium):
пространство имен википедия { статья класса { общедоступная : std :: string format (); // = _ZN9wikipedia7article6formatEv bool print_to ( std :: ostream & ); // = _ZN9wikipedia7article8print_toERSo класс wikilink { общедоступный : wikilink ( std :: string const & name ); // = _ZN9wikipedia7article8wikilinkC1ERKSs }; }; }
Все искаженные символы начинаются с _Z
(обратите внимание, что идентификатор, начинающийся с подчеркивания, за которым следует заглавная буква, является зарезервированным идентификатором в C, поэтому конфликта с идентификаторами пользователей можно избежать); для вложенных имен ( в том числе обоих пространств имен и классов), за этим следует N
, то ряд <длина>, ID пар (длиной является длиной следующего идентификатора), и , наконец E
. Например, wikipedia::article::format
становится:
_ZN9wikipedia7article6formatE
Для функций после этого следует информация о типе; а format()
это void
функция, это просто v
; следовательно:
_ZN9wikipedia7article6formatEv
For print_to
используется стандартный тип std::ostream
(который является typedef для std::basic_ostream
) со специальным псевдонимом So
; ссылка на этот тип, таким образом RSo
, с полным именем функции:
_ZN9wikipedia7article8print_toERSo
Как разные компиляторы искажают одни и те же функции
Не существует стандартизированной схемы, с помощью которой искажаются даже тривиальные идентификаторы C ++, и, следовательно, разные компиляторы (или даже разные версии одного и того же компилятора или один и тот же компилятор на разных платформах) искажают общедоступные символы в радикально разных (и, следовательно, полностью несовместимых) способами. Рассмотрим, как разные компиляторы C ++ искажают одни и те же функции:
Компилятор | void h(int) | void h(int, char) | void h(void) |
---|---|---|---|
Intel C ++ 8.0 для Linux | _Z1hi | _Z1hic | _Z1hv |
HP aC ++ A.05.55 IA-64 | |||
IAR EWARM C ++ 5.4 ARM | |||
GCC 3. x и выше | |||
Clang 1. x и выше [3] | |||
IAR EWARM C ++ 7.4 ARM | _Z | _Z | _Z |
GCC 2.9. Икс | h__Fi | h__Fic | h__Fv |
HP aC ++ A.03.45 PA-RISC | |||
Microsoft Visual C ++ v6-v10 ( детализация ) | ?h@@YAXH@Z | ?h@@YAXHD@Z | ?h@@YAXXZ |
Цифровой Марс C ++ | |||
Borland C ++ v3.1 | @h$qi | @h$qizc | @h$qv |
OpenVMS C ++ v6.5 (режим ARM) | H__XI | H__XIC | H__XV |
OpenVMS C ++ v6.5 (режим 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 (режим ARM) | h__Xi | h__Xic | h__Xv |
Tru64 C ++ v6.5 (режим 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 |
Заметки:
- Компилятор Compaq C ++ для OpenVMS VAX и Alpha (но не IA-64) и Tru64 имеет две схемы изменения имен. Исходная предварительная стандартная схема известна как модель ARM и основана на изменении имен, описанном в Аннотированном справочном руководстве C ++ (ARM). С появлением новых функций в стандартном C ++, в частности шаблонов , схема ARM становилась все более и более непригодной - она не могла кодировать определенные типы функций или выдавать идентично искаженные имена для разных функций. Поэтому она была заменена более новой моделью «ANSI», которая поддерживала все функции шаблона ANSI, но не была обратно совместимой.
- На IA-64 существует стандартный двоичный интерфейс приложения (ABI) (см. Внешние ссылки ), который определяет (среди прочего) стандартную схему изменения имен и используется всеми компиляторами IA-64. GNU GCC 3. x , кроме того, принял схему изменения имен, определенную в этом стандарте, для использования на других платформах, отличных от Intel.
- Visual Studio и Windows SDK включают программу,
undname
которая печатает прототип функции в стиле C для заданного искаженного имени. - В Microsoft Windows компилятор Intel [4] и Clang [5] используют изменение имен Visual C ++ для совместимости.
- Для компилятора ARM C ++ 7.4 IAR EWARM лучший способ определить имя функции - это выполнить компиляцию с включенным выходом ассемблера и просмотреть выходные данные в сгенерированном таким образом файле ".s".
Обработка символов C при линковке из C ++
Работа общей идиомы C ++:
#ifdef __cplusplus extern "C" { #endif / * ... * / #ifdef __cplusplus } #endif
состоит в том, чтобы гарантировать, что символы внутри "не запутаны" - что компилятор испускает двоичный файл с их именами без декорирования, как это сделал бы компилятор C. Поскольку определения языка C не связаны, компилятор C ++ должен избегать искажения ссылок на эти идентификаторы.
Например, стандартная библиотека строк
, обычно содержит что-то вроде:
#ifdef __cplusplus extern "C" { #endifvoid * memset ( void * , int , size_t ); char * strcat ( char * , const char * ); int strcmp ( const char * , const char * ); char * strcpy ( char * , const char * );#ifdef __cplusplus } #endif
Таким образом, такой код, как:
если ( strcmp ( argv [ 1 ], "-x" ) == 0 ) strcpy ( a , argv [ 2 ]); иначе memset ( a , 0 , sizeof ( a ));
использует правильный, не запутанный strcmp
и memset
. Если extern "C"
бы не использовался, компилятор (SunPro) C ++ создал бы код, эквивалентный:
если ( __1cGstrcmp6Fpkc1_i_ ( argv [ 1 ], "-x" ) == 0 ) __1cGstrcpy6Fpcpkc_0_ ( a , argv [ 2 ]); иначе __1cGmemset6FpviI_0_ ( a , 0 , sizeof ( a ));
Поскольку эти символы не существуют в библиотеке времени выполнения C ( например, libc), возникнут ошибки компоновки.
Стандартизированное изменение имен в C ++
Казалось бы, стандартизованное изменение имен в языке C ++ приведет к большей совместимости между реализациями компилятора. Однако такая стандартизация сама по себе недостаточна, чтобы гарантировать совместимость компилятора C ++, и может даже создать ложное впечатление, что взаимодействие возможно и безопасно, когда это не так. Изменение имени - это лишь одна из нескольких деталей двоичного интерфейса приложения (ABI), которые должны быть определены и соблюдены реализацией C ++. Другие аспекты ABI, такие как обработка исключений , макет виртуальной таблицы , заполнение структуры и кадра стека и т. Д., Также приводят к несовместимости различных реализаций C ++. Кроме того, требование определенной формы искажения вызовет проблемы для систем, в которых ограничения реализации (например, длина символов) диктуют конкретную схему преобразования. Стандартизованное требование к изменению имен также предотвратит реализацию, в которой искажение вообще не требуется - например, компоновщик, который понимает язык C ++.
Поэтому стандарт C ++ не пытается стандартизировать изменение имен. Напротив, Справочное руководство по C ++ с аннотациями (также известное как ARM , ISBN 0-201-51459-1 , раздел 7.2.1c) активно поощряет использование различных схем преобразования для предотвращения связывания, когда другие аспекты ABI несовместимы.
Тем не менее, как подробно описано в разделе выше, на некоторых платформах [6] полный C ++ ABI стандартизирован, включая изменение имен.
Реальные эффекты искажения имен C ++
Поскольку символы C ++ обычно экспортируются из DLL и общих объектных файлов, схема изменения имен - это не просто внутреннее дело компилятора. Различные компиляторы (или разные версии одного и того же компилятора во многих случаях) создают такие двоичные файлы с разными схемами оформления имен, что означает, что символы часто не разрешаются, если компиляторы, использованные для создания библиотеки, и программа, использующая ее, использовали разные схемы. Например, если система с несколькими установленными компиляторами C ++ (например, GNU GCC и компилятор поставщика ОС) желает установить библиотеки Boost C ++ , ее придется скомпилировать несколько раз (один раз для GCC и один раз для компилятора поставщика).
В целях безопасности полезно, чтобы компиляторы, производящие несовместимые объектные коды (коды, основанные на разных ABI, например, в отношении классов и исключений), использовали разные схемы изменения имен. Это гарантирует, что эти несовместимости обнаруживаются на этапе связывания, а не при запуске программного обеспечения (что может привести к неясным ошибкам и серьезным проблемам со стабильностью).
По этой причине оформление имен является важным аспектом любого связанного с C ++ ABI .
Разобрать через фильтр c ++
$ c ++ filter -n _ZNK3MapI10StringName3RefI8GDScriptE10ComparatorIS0_E16DefaultAllocatorE3hasERKS0_ Map , Comparator , DefaultAllocator> :: has (StringName const &) const ,>
Разобрать через встроенный GCC ABI
#include #include #include int main () { const char * mangled_name = "_ZNK3MapI10StringName3RefI8GDScriptE10ComparatorIS0_E16DefaultAllocatorE3hasERKS0_" ; int status = -1 ; символ * demangled_name = аби :: __cxa_demangle ( mangled_name , NULL , NULL , и статус ); printf ( "Demangled:% s \ n " , demangled_name ); бесплатно ( demangled_name ); возврат 0 ; }
Выход:
Demangled: Map, Comparator, DefaultAllocator>::has(StringName const&) const ,>
Ява
В Java сигнатура метода или класса содержит его имя и типы аргументов метода и возвращаемого значения, где это применимо. Формат подписей задокументирован, поскольку язык, компилятор и формат файла .class были разработаны вместе (и с самого начала имели в виду объектную ориентацию и универсальную совместимость).
Создание уникальных имен для внутренних и анонимных классов
Область действия анонимных классов ограничена их родительским классом, поэтому компилятор должен создать «квалифицированное» публичное имя для внутреннего класса, чтобы избежать конфликта, когда другие классы с таким же именем (внутренним или нет) существуют в том же пространстве имен. Точно так же анонимные классы должны иметь сгенерированные для них «поддельные» общедоступные имена (поскольку концепция анонимных классов существует только в компиляторе, а не во время выполнения). Итак, компилируем следующую java-программу
общедоступный класс foo { класс бар { общедоступный int x ; } public void zark () { объект f = новый объект () { public String toString () { вернуть "привет" ; } }; } }
создаст три файла .class :
- foo.class , содержащий основной (внешний) класс foo
- foo $ bar.class , содержащий названный внутренний класс foo.bar
- foo $ 1.class , содержащий анонимный внутренний класс (локальный для метода foo.zark )
Все эти имена классов допустимы (поскольку символы $ разрешены в спецификации JVM), и эти имена «безопасны» для генерации компилятором, поскольку определение языка Java рекомендует не использовать символы $ в обычных определениях классов java.
Разрешение имен в Java еще более усложняется во время выполнения, поскольку полностью определенные имена классов уникальны только внутри конкретного экземпляра загрузчика классов . Загрузчики классов упорядочены иерархически, и каждый поток в JVM имеет так называемый загрузчик классов контекста, поэтому в случаях, когда два разных экземпляра загрузчика классов содержат классы с одинаковым именем, система сначала пытается загрузить класс с помощью корневого (или системного) загрузчика классов. а затем переходит вниз по иерархии к загрузчику класса контекста.
Собственный интерфейс Java
Поддержка собственных методов Java позволяет программам на языке Java вызывать программы, написанные на другом языке (обычно C или C ++). Здесь есть две проблемы разрешения имен, ни одна из которых не реализована особенно стандартным образом:
- Перевод JVM в родное имя - это кажется более стабильным, поскольку Oracle делает свою схему общедоступной. [7]
- Обычное изменение имени C ++ - см. Выше.
Python
В Python искажение используется для атрибутов класса, которые не должны использоваться подклассами [8], которые обозначаются как таковые, давая им имя с двумя ведущими символами подчеркивания и не более чем одним подчеркиванием в конце. Например, __thing
будет покалечено, как будет ___thing
и __thing_
, но так __thing__
и __thing___
не будет. Среда выполнения Python не ограничивает доступ к таким атрибутам, изменение предотвращает конфликты имен только в том случае, если производный класс определяет атрибут с тем же именем.
При обнаружении искаженных атрибутов имени Python преобразует эти имена, добавляя перед ним один знак подчеркивания и имя включающего класса, например:
>>> class Test : ... def __mangled_name ( self ): ... pass ... def normal_name ( self ): ... pass >>> t = Test () >>> [ attr for attr in dir ( t ) если "name" в attr ] ['_Test__mangled_name', 'normal_name']
Паскаль
Диапазон Turbo Pascal / Delphi от Borland
Чтобы избежать искажения имен в Паскале, используйте:
экс порта MyFunc название 'MyFunc' , MyProc имя 'MyProc' ;
Свободный Паскаль
Free Pascal поддерживает перегрузку функций и операторов, поэтому он также использует изменение имен для поддержки этих функций. С другой стороны, Free Pascal способен вызывать символы, определенные во внешних модулях, созданных на другом языке, и экспортировать свои собственные символы для вызова другим языком. Для получения более подробной информации, обратитесь к главе 6.2 и 7.1 из Руководства Free Pascal программиста .
Фортран
Изменение имен также необходимо в компиляторах Фортрана , изначально потому, что язык нечувствителен к регистру . Дальнейшие требования к изменению были наложены позже в процессе развития языка из-за добавления модулей и других функций в стандарт Fortran 90. Дело коверкание, особенно, является общим вопросом , который необходимо решать, чтобы вызвать Fortran библиотеки, такие как LAPACK , от других языков, таких как C .
Из-за нечувствительности к регистру имя подпрограммы или функции FOO
должно быть преобразовано компилятором в стандартизированный регистр и формат, чтобы оно было связано одинаково независимо от регистра. Различные компиляторы реализовали это по-разному, и никакой стандартизации не произошло. В AIX и HP-UX Составители Fortran конвертировать все идентификаторы в нижнем регистре foo
, в то время как Cray и Unicos составители Fortran преобразуются идентификаторы в верхний регистр FOO
. В GNU g77 компилятор преобразует идентификаторы в нижний регистр плюс подчеркивание foo_
, за исключением того, что идентификаторы , уже содержащих подчеркивание FOO_BAR
имеют два подчеркивания приложенного foo_bar__
, следуя конвенции , установленной f2c . Многие другие компиляторы, в том числе SGI «s IRIX компиляторов GNU Fortran и Intel » компилятор Фортрана s ( за исключением Microsoft Windows), преобразующие все идентификаторы в нижний регистр плюс подчеркивание ( foo_
и foo_bar_
, соответственно). В Microsoft Windows компилятор Intel Fortran по умолчанию использует прописные буквы без подчеркивания. [9]
Идентификаторы в модулях Fortran 90 необходимо дополнительно изменить, потому что одно и то же имя процедуры может встречаться в разных модулях. Поскольку стандарт Fortran 2003 требует, чтобы имена процедур модуля не конфликтовали с другими внешними символами, [10] компиляторы обычно используют имя модуля и имя процедуры с отдельным маркером между ними. Например:
модуль m содержит целочисленную функцию five () five = 5 end function five end module m
В этом модуле имя функции будет изменено как __m_MOD_five
(например, GNU Fortran), m_MP_five_
(например, ifort Intel), m.five_
(например, Oracle sun95) и т. Д. Поскольку Fortran не позволяет перегрузить имя процедуры, но использует вместо универсальных интерфейсных блоков и универсальных процедур, связанных с типом, искаженные имена не должны включать подсказки об аргументах.
Параметр BIND Fortran 2003 отменяет любое изменение имени, выполняемое компилятором, как показано выше .
Ржавчина
Имена функций в Rust по умолчанию изменяются . Однако это можно отключить с помощью #[no_mangle]
атрибута function. Этот атрибут можно использовать для экспорта функций в C, C ++ или Objective-C. [11] Кроме того, вместе с #[start]
атрибутом функции или атрибутом #[no_main]
crate, он позволяет пользователю определять точку входа в C-стиле для программы. [12]
Rust использовал множество версий схем изменения символов, которые можно выбрать во время компиляции с помощью -Z symbol-mangling-version
опции. Определены следующие манглеры:
legacy
Изменение стиля C ++ на основе Itanium IA-64 C ++ ABI. Символы начинаются с_ZN
, а хеши файлов используются для устранения неоднозначности. Используется с Rust 1.9. [13]v0
Улучшенная версия устаревшей схемы с изменениями для Rust. Символы начинаются с_R
. Полиморфизм можно закодировать. Функции не имеют закодированных возвращаемых типов (в Rust нет перегрузки). Имена Unicode используют модифицированный punycode . Сжатие (обратная ссылка) использует байтовую адресацию. Используется с Rust 1.37. [14]
Примеры представлены в symbol-names
тестах на Rust . [15]
Цель-C
По существу две формы метод существует в Objective-C , тем класс ( «статический») метод , а также методы экземпляра . Объявление метода в Objective-C имеет следующую форму:
+ ( возвращаемый тип ) имя 0 : параметр 0 имя 1 : параметр 1 ...- ( возвращаемый тип ) имя 0 : параметр 0 имя 1 : параметр 1 ...
Методы класса обозначаются знаком +, методы экземпляра используют -. Тогда типичное объявление метода класса может выглядеть так:
+ ( id ) initWithX: ( int ) число andY: ( int ) число ; + ( id ) новый ;
С методами экземпляра, которые выглядят так:
- ( id ) значение ; - ( id ) setValue: ( id ) new_value ;
Каждое из этих объявлений метода имеет определенное внутреннее представление. При компиляции каждый метод получает имя в соответствии со следующей схемой для методов класса:
_c_ Класс _ имя 0 _ имя 1 _ ...
и это, например, методы:
_i_ Класс _ имя 0 _ имя 1 _ ...
Двоеточия в синтаксисе Objective-C переводятся в символы подчеркивания. Таким образом, метод класса Objective-C , если он принадлежит к классу, будет преобразован как , а метод экземпляра (принадлежащий к тому же классу) будет преобразован в .+ (id) initWithX: (int) number andY: (int) number;
Point
_c_Point_initWithX_andY_
- (id) value;
_i_Point_value
Таким образом помечается каждый из методов класса. Однако поиск метода, на который может ответить класс, было бы утомительным, если бы все методы были представлены таким образом. Каждому из методов присваивается уникальный символ (например, целое число). Такой символ называется селектором . В Objective-C можно напрямую управлять селекторами - они имеют определенный тип в Objective-C - SEL
.
Во время компиляции создается таблица, которая сопоставляет текстовое представление, например _i_Point_value
, селекторам (которым присваивается тип SEL
). Управление селекторами более эффективно, чем управление текстовым представлением метода. Обратите внимание, что селектор соответствует только имени метода, а не классу, к которому он принадлежит - разные классы могут иметь разные реализации метода с одинаковым именем. Из-за этого реализациям метода также присваивается определенный идентификатор, они известны как указатели реализации, и им также присваивается тип IMP
.
Отправленные сообщения кодируются компилятором как вызовы функции или одного из ее кузенов, где находится получатель сообщения и определяет метод для вызова. У каждого класса есть своя собственная таблица, в которой селекторы сопоставляются с их реализациями - указатель реализации указывает, где в памяти находится фактическая реализация метода. Существуют отдельные таблицы для методов класса и экземпляра. Помимо того , хранится в в справочных таблицах, функции, по существу , анонимно.id objc_msgSend (id receiver, SEL selector, ...)
receiver
SEL
SEL
IMP
SEL
Значение селектора не изменяется между классами. Это позволяет полиморфизм .
Среда выполнения Objective-C хранит информацию об аргументах и типах возвращаемых методов. Однако эта информация не является частью имени метода и может варьироваться от класса к классу.
Поскольку Objective-C не поддерживает пространства имен , нет необходимости изменять имена классов (которые отображаются как символы в сгенерированных двоичных файлах).
Быстрый
Swift хранит метаданные о функциях (и многом другом) в искаженных символах, относящихся к ним. Эти метаданные включают имя функции, атрибуты, имя модуля, типы параметров, тип возвращаемого значения и многое другое. Например:
Искаженное имя метода func calculate(x: int) -> int
в виде MyClass
класса в модуле test
есть _TFC4test7MyClass9calculatefS0_FT1xSi_Si
, на 2014 Swift. Компоненты и их значения следующие: [16]
_T
: Префикс для всех символов Swift. Все начнется с этого.F
: Функция без каррирования.C
: Функция класса, т.е. метод4test
: Имя модуля с префиксом его длины.7MyClass
: Имя класса, к которому функция принадлежит, с префиксом его длины.9calculate
: Имя функции с префиксом ее длины.f
: Атрибут функции. В данном случае «f» означает нормальную функцию.S0
: Обозначает тип первого параметра (а именно, экземпляр класса) как первый в стеке типов (здесьMyClass
не является вложенным и, следовательно, имеет индекс 0)._FT
: Начинается список типов для кортежа параметров функции.1x
: Внешнее имя первого параметра функции.Si
: Указывает встроенный тип Swift Swift.Int для первого параметра._Si
: Тип возврата: снова Swift.Int.
Исправление для версий, начиная с Swift 4.0, официально задокументировано. Он сохраняет некоторое сходство с Itanium. [17]
Смотрите также
- Интерфейс прикладного программирования
- Двоичный интерфейс приложения
- Соглашение о вызове
- Сравнение виртуальных машин приложений
- Интерфейс внешней функции
- Собственный интерфейс Java
- Привязка к языку
- Строппинг
- SWIG
Рекомендации
- ^ «Двоичный интерфейс приложения - язык программирования D» . dlang.org . Проверено 19 мая 2020 .
- ^ Райнер, Шуэце. "Новомодное искажение имени Ди" . D Блог . Проверено 19 мая 2020 .
- ^ Clang - Возможности и цели: совместимость с GCC , 15 апреля 2013 г.
- ^ JBIntel_deleted_06032015. «Различия OBJ между компилятором Intel и компилятором VC» . software.intel.com .
- ^ «Совместимость с MSVC» . Дата обращения 13 мая 2016 .
- ^ «Itanium C ++ ABI, раздел 5.1. Внешние имена (также известные как Mangling)» . Дата обращения 16 мая 2016 .
- ^ «Обзор дизайна» . docs.oracle.com .
- ^ https://www.python.org/dev/peps/pep-0008/#designing-for-inheritance
- ^ «Краткое изложение вопросов, связанных с использованием разных языков» . Руководство пользователя и справочное руководство для компилятора Intel Fortran 15.0 . Корпорация Intel . Проверено 17 ноября 2014 года .
- ^ https://software.intel.com/en-us/node/510637
- ^ "Интерфейс внешних функций # Вызов кода Rust из C" . Руководство по Rust . rust-lang.org . Дата обращения 13 мая 2016 .
- ^ "Нет stdlib" . Руководство по Rust . rust-lang.org . Дата обращения 13 мая 2016 .
- ^ "rust / src / librustc_codegen_utils / symbol_names / legacy.r.rs at 57e1da59cd0761330b4ea8d47b16340a78eeafa9 · rust-lang / rust · GitHub" .
- ^ "Искажение символа ржавчины" . Книга RFC Rust .
- ^ "Rust 1.42.0: src / test / ui / symbol-names" . Github . Проверено 20 марта 2020 года .
- ^ «mikeash.com: Пятница, вопросы и ответы, 2014-08-15: Swift Name Mangling» . mikeash.com .
- ^ "яблоко / свифт: mangling.rst" . GitHub .
Внешние ссылки
- Linux Itanium ABI для C ++ , включая схему изменения имен.
- Стандартная спецификация Macintosh C / C ++ ABI
- c ++ filter - фильтр для извлечения кодированных символов C ++ для компиляторов GNU / Intel
- undname - инструмент msvc для разборки имен.
- demangler.com - онлайн-инструмент для разборки символов GCC и MSVC C ++
- Система времени выполнения Objective-C - из языка программирования Apple Objective-C 1.0
- Соглашения о вызовах для различных компиляторов C ++ от Agner Fog содержат подробное описание схем изменения имен для различных компиляторов C ++ x86 и x64 (стр. 24–42 в версии 2011-06-08)
- C ++ Name Mangling / Demangling Довольно подробное объяснение схемы изменения имени компилятора Visual C ++
- PHP UnDecorateSymbolName - сценарий php, который разоблачает имена функций Microsoft Visual C.
- Смешивание кода C и C ++
- Левин, Джон Р. (2000) [октябрь 1999]. «Глава 5: Управление символами» . Линкеры и загрузчики . Серия Морган Кауфманн в программной инженерии и программировании (1-е изд.). Сан-Франциско, США: Морган Кауфманн . ISBN 1-55860-496-0. OCLC 42413382 . ISBN 978-1-55860-496-4 . Архивировано 05 декабря 2012 года . Проверено 12 января 2020 .Код: [1] [2] Ошибки: [3]
- Фивос Кефалонитис демистифицирует искажение имен