Искажение имени
![]() | В этой статье есть несколько проблем. Пожалуйста, помогите улучшить его или обсудите эти проблемы на странице обсуждения . ( Узнайте, как и когда удалять эти шаблонные сообщения )
|
В компилятора конструкции искажение имен (также называемое украшением имени ) — это метод, используемый для решения различных проблем, вызванных необходимостью разрешения уникальных имен для объектов программирования во многих современных языках программирования .
Он предоставляет средства для кодирования добавленной информации в имя функции , структуры , класса или другого типа данных , чтобы передать больше семантической информации от компилятора к компоновщику .
Необходимость в искажении имен возникает, когда язык позволяет называть разные объекты одним и тем же идентификатором , пока они занимают другое пространство имен (обычно определяемое модулем, классом или явной директивой пространства имен ) или имеют разные сигнатуры типов (например, в перегрузка функции ). Это необходимо в таких случаях, поскольку для каждой подписи может потребоваться различное специализированное соглашение о вызовах в машинном коде .
Любой объектный код , созданный компиляторами, обычно связывается с другими частями объектного кода (созданными тем же или другим компилятором) с помощью программы, называемой компоновщиком . Компоновщику требуется большой объем информации о каждом программном объекте. Например, чтобы правильно связать функцию, ей нужно ее имя, количество аргументов и их типы и так далее.
Простые языки программирования 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
будет преобразовано в одно и то же имя, поэтому компоновщик не обнаружит эту проблему, и результатом обычно будет сбой или повреждение данных или памяти во время выполнения.
Примеры [ править ]
![]() | Этот раздел написан как руководство или руководство . ( сентябрь 2016 г. ) |
С [ править ]
Хотя искажение имен обычно не требуется и не используется в языках, которые не поддерживают перегрузку функций , таких как C и классический Pascal , они используют его в некоторых случаях для предоставления дополнительной информации о функции. Например, компиляторы, предназначенные для платформ Microsoft Windows , поддерживают различные соглашения о вызовах , которые определяют способ отправки параметров в подпрограммы и возврата результатов. Поскольку различные соглашения о вызовах несовместимы друг с другом, компиляторы искажают символы кодами, подробно описывающими, какое соглашение следует использовать для вызова конкретной процедуры.
Схема искажения для Windows была создана Microsoft, и ей неофициально следовали другие компиляторы, включая Digital Mars , Borland и GNU Compiler Collection (GCC), при компиляции кода для платформ Windows. Схема применима даже к другим языкам, таким как Pascal , D , Delphi , Fortran и C# . Это позволяет подпрограммам, написанным на этих языках, вызывать существующие библиотеки Windows, используя соглашение о вызовах, отличное от используемого по умолчанию.
При компиляции следующих примеров C:
int _cdecl f (int x) { return 0; }
int _stdcall g (int y) { return 0; }
int _fastcall h (int z) { return 0; }
32-битные компиляторы выдают соответственно:
_f _g@4 @h@4
В stdcall
и fastcall
схем искажения, функция кодируется как _name@X
и @name@X
соответственно, где X — количество байтов в десятичном формате аргумента(ов) в списке параметров (включая переданные в регистрах для быстрого вызова). В случае cdecl
, имя функции просто начинается с подчеркивания.
В 64-битном соглашении Windows (Microsoft C) нет ведущего подчеркивания. Эта разница может в некоторых редких случаях привести к неразрешенным внешним проблемам при переносе такого кода на 64-битную версию. Например, код Фортрана может использовать «псевдоним» для связи с методом C по имени следующим образом:
SUBROUTINE f()
!DEC$ ATTRIBUTES C, ALIAS:'_f' :: f
END SUBROUTINE
Это будет нормально компилироваться и компоноваться под 32-битной версией, но генерировать неразрешенный внешний файл. _f
под 64 бита. Одним из способов решения этой проблемы является вообще не использовать «псевдоним» (в котором имена методов обычно должны быть написаны с заглавной буквы в C и Fortran). Другой способ — использовать опцию BIND:
SUBROUTINE f() BIND(C,NAME="f")
END SUBROUTINE
В C большинство компиляторов также преобразуют статические функции и переменные (а в C++ функции и переменные, объявленные как статические или помещенные в анонимное пространство имен) в единицах перевода, используя те же правила преобразования, что и для их нестатических версий. Если функции с одинаковым именем (и параметрами для C++) также определены и используются в разных единицах перевода, это также приведет к тому же имени, что потенциально может привести к конфликту. Однако они не будут эквивалентны, если вызываются в соответствующих единицах перевода. Компиляторы обычно могут произвольно искажать эти функции, поскольку прямой доступ к ним из других единиц трансляции незаконен, поэтому им никогда не потребуется связывание между различными объектными кодами (их связывание никогда не требуется). Чтобы предотвратить конфликты ссылок, компиляторы будут использовать стандартное искажение, но будут использовать так называемые «локальные» символы. При связывании многих таких единиц перевода может быть несколько определений функции с одним и тем же именем, но результирующий код будет вызывать только одно или другое в зависимости от того, из какой единицы перевода он получен. Обычно это делается с помощью перемещения механизм .
С++ [ править ]
Компиляторы C++ являются наиболее распространенными пользователями искажения имен. Первые компиляторы C++ были реализованы как трансляторы исходного кода C , который затем компилировался компилятором C в объектный код; из-за этого имена символов должны были соответствовать правилам идентификаторов C. Даже позже, с появлением компиляторов, которые создавали машинный код или ассемблер напрямую, системный компоновщик обычно не поддерживал символы C++, и искажения по-прежнему требовались.
Язык C++ не определяет стандартную схему оформления, поэтому каждый компилятор использует свою собственную. C++ также имеет сложные функции языка, такие как классы , шаблоны , пространства имен и перегрузка операторов , которые изменяют значение определенных символов в зависимости от контекста или использования. Метаданные об этих функциях можно устранить, исказив (украсив) имя символа . Поскольку системы изменения имен для таких функций не стандартизированы для разных компиляторов, немногие компоновщики могут связывать объектный код, созданный разными компиляторами.
Простой пример [ править ]
Одна единица трансляции C++ может определять две функции с именами f()
:
int f () { return 1; }
int f (int) { return 0; }
void g () { int i = f(), j = f(0); }
Это отдельные функции, не имеющие никакой связи друг с другом, кроме названия. Поэтому компилятор C++ закодирует информацию о типе в имени символа, в результате чего получится что-то вроде:
int __f_v () { return 1; }
int __f_i (int) { return 0; }
void __g_v () { int i = __f_v(), j = __f_i(0); }
Несмотря на то, что его имя уникально, g()
все еще искажено: искажение имен применяется ко всем символам C++ (за исключением символов в extern "C"{}
блокировать).
Сложный пример [ править ]
Искаженные символы в этом примере в комментариях под соответствующим именем идентификатора созданы компиляторами GNU GCC 3.x в соответствии с ABI IA-64 (Itanium):
namespace wikipedia
{
class article
{
public:
std::string format (); // = _ZN9wikipedia7article6formatEv
bool print_to (std::ostream&); // = _ZN9wikipedia7article8print_toERSo
class wikilink
{
public:
wikilink (std::string const& name); // = _ZN9wikipedia7article8wikilinkC1ERKSs
};
};
}
Все искаженные символы начинаются с _Z
(обратите внимание, что идентификатор, начинающийся с подчеркивания, за которым следует заглавная буква, является зарезервированным идентификатором в C, поэтому можно избежать конфликта с идентификаторами пользователя); для вложенных имен (включая пространства имен и классы) за ним следует N
, затем серия пар <length, id> (длина равна длине следующего идентификатора) и, наконец, E
. Например, wikipedia::article::format
становится:
_ZN9wikipedia7article6formatE
Для функций за этим следует информация о типе; как format()
это void
функция, это просто v
; следовательно:
_ZN9wikipedia7article6formatEv
Для print_to
, стандартный тип std::ostream
(что представляет собой определение типа для std::basic_ostream<char, std::char_traits<char> >
) используется специальный псевдоним 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 | |||
И EWARM C++ | |||
GCC 3.x и выше | |||
Clang 1.x и выше [3] | |||
ОКС 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++ | |||
Борланд С++ 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 С++ X7.1 IA-64 | CXX$_Z1HI2DSQ26A
|
CXX$_Z1HIC2NP3LI4
|
CXX$_Z1HV0BCA19V
|
СанПро 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
|
Ватком С++ 10.6 | W?h$n(i)v
|
W?h$n(ia)v
|
W?h$n()v
|
Примечания:
- Компилятор Compaq (но не IA - C++ для OpenVMS VAX и Alpha 64) и Tru64 UNIX имеет две схемы искажения имен. Исходная предстандартная схема известна как модель 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] и Кланг [5] использует преобразование имен Visual C++ для совместимости.
Обработка символов C при связывании с C++ [ править ]
Работа общей идиомы C++:
#ifdef __cplusplus
extern "C" {
#endif
/* ... */
#ifdef __cplusplus
}
#endif
заключается в том, чтобы гарантировать, что символы внутри «неискажены» - что компилятор создает двоичный файл с недекорированными именами, как это сделал бы компилятор C. Поскольку определения языка C не искажены, компилятору C++ необходимо избегать искажения ссылок на эти идентификаторы.
Например, стандартная библиотека строк, <string.h>
, обычно содержит что-то вроде:
#ifdef __cplusplus
extern "C" {
#endif
void *memset (void *, int, size_t);
char *strcat (char *, const char *);
int strcmp (const char *, const char *);
char *strcpy (char *, const char *);
#ifdef __cplusplus
}
#endif
Таким образом, такой код, как:
if (strcmp(argv[1], "-x") == 0)
strcpy(a, argv[2]);
else
memset (a, 0, sizeof(a));
использует правильный, неиспорченный strcmp
и memset
. Если extern "C"
не использовался, компилятор (SunPro) C++ выдал бы код, эквивалентный:
if (__1cGstrcmp6Fpkc1_i_(argv[1], "-x") == 0)
__1cGstrcpy6Fpcpkc_0_(a, argv[2]);
else
__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, например, в отношении классов и исключений), использовали разные схемы искажения имен. Это гарантирует, что эти несовместимости будут обнаружены на этапе компоновки, а не при запуске программного обеспечения (что может привести к неявным ошибкам и серьезным проблемам со стабильностью).
По этой причине украшение имени является важным аспектом любого ABI , связанного с C++ .
Бывают случаи, особенно в больших и сложных базах кода, когда может быть сложно или непрактично сопоставить искаженное имя, выдаваемое в сообщении об ошибке компоновщика, обратно с конкретным соответствующим именем токена/переменной в источнике. Эта проблема может затруднить идентификацию соответствующих исходных файлов для инженеров по сборке или тестированию, даже если используются только один компилятор и компоновщик. Деманглеры (в том числе входящие в механизмы сообщения об ошибках компоновщика) иногда помогают, но сам механизм искажения может отбросить важную информацию, устраняющую неоднозначность.
Разборка с помощью c++filt [ править ]
$ c++filt -n _ZNK3MapI10StringName3RefI8GDScriptE10ComparatorIS0_E16DefaultAllocatorE3hasERKS0_
Map<StringName, Ref<GDScript>, Comparator<StringName>, DefaultAllocator>::has(StringName const&) const
Деманглирование через встроенный GCC ABI [ править ]
#include <stdio.h>
#include <stdlib.h>
#include <cxxabi.h>
int main() {
const char *mangled_name = "_ZNK3MapI10StringName3RefI8GDScriptE10ComparatorIS0_E16DefaultAllocatorE3hasERKS0_";
int status = -1;
char *demangled_name = abi::__cxa_demangle(mangled_name, NULL, NULL, &status);
printf("Demangled: %s\n", demangled_name);
free(demangled_name);
return 0;
}
Выход:
Demangled: Map<StringName, Ref<GDScript>, Comparator<StringName>, DefaultAllocator>::has(StringName const&) const
Ява [ править ]
В Java подпись . метода или класса содержит его имя, типы аргументов метода и возвращаемое значение, если это применимо Формат подписей документирован, поскольку язык, компилятор и формат файла .class были разработаны вместе (и с самого начала учитывались объектная ориентация и универсальная совместимость).
Создание уникальных имен для внутренних и анонимных классов [ править ]
Область действия анонимных классов ограничена их родительским классом, поэтому компилятор должен создать «полное» общедоступное имя для внутреннего класса , чтобы избежать конфликта, когда другие классы с тем же именем (внутренним или нет) существуют в том же пространстве имен. Аналогично, анонимные классы должны иметь «поддельные» общедоступные имена, сгенерированные для них (поскольку концепция анонимных классов существует только в компиляторе, а не во время выполнения). Итак, компилируем следующую Java-программу:
public class foo {
class bar {
public int x;
}
public void zark () {
Object f = new Object () {
public String toString() {
return "hello";
}
};
}
}
создаст три .class файла :
- foo.class , содержащий основной (внешний) класс foo
- foo$bar.class , содержащий именованный внутренний класс foo.bar
- foo$1.class , содержащий анонимный внутренний класс (локальный для метода foo.zark )
Все эти имена классов действительны (поскольку символы $ разрешены в спецификации JVM), и эти имена «безопасны» для генерации компилятором, поскольку определение языка Java рекомендует не использовать символы $ в обычных определениях классов Java.
Разрешение имен в Java еще более усложняется во время выполнения, поскольку полные имена классов уникальны только внутри конкретного экземпляра загрузчика классов . Загрузчики классов упорядочены иерархически, и каждый поток в JVM имеет так называемый контекстный загрузчик классов, поэтому в случаях, когда два разных экземпляра загрузчика классов содержат классы с одинаковым именем, система сначала пытается загрузить класс, используя корневой (или системный) загрузчик классов. а затем спускается по иерархии к загрузчику классов контекста.
Собственный интерфейс Java [ править ]
Java Native Interface , встроенная поддержка методов Java, позволяет программам на языке Java вызывать программы, написанные на другом языке (обычно C или C++). Здесь есть две проблемы с разрешением имен, ни одна из которых не реализована стандартизированным образом :
- Преобразование имен JVM в собственное имя — это кажется более стабильным, поскольку Oracle делает свою схему общедоступной. [7]
- Обычное искажение имен C++ — см. выше.
Питон [ править ]
В 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) if "name" in attr]
['_Test__mangled_name', 'normal_name']
Паскаль [ править ]
Турбо Паскаль, Delphi [ править ]
Чтобы избежать искажения имен в Паскале, используйте:
exports
myFunc name 'myFunc',
myProc name 'myProc';
Бесплатный Паскаль [ править ]
Free Pascal поддерживает перегрузку функций и операторов, поэтому для поддержки этих функций он также использует преобразование имен. С другой стороны, Free Pascal способен вызывать символы, определенные во внешних модулях, созданных на другом языке, и экспортировать свои собственные символы для вызова на другом языке. Для получения дополнительной информации обратитесь к главам 6.2 и 7.1 Руководства программиста Free Pascal .
Фортран [ править ]
Искажение имен также необходимо в компиляторах Фортрана , поскольку язык нечувствителен к регистру . Дальнейшие требования к искажению были наложены позже в ходе развития языка из-за добавления модулей и других функций в стандарт Fortran 90. В частности, искажение регистра является распространенной проблемой, которую необходимо решать при вызове библиотек Фортрана, таких как LAPACK , из других языков, таких C. как
Из-за нечувствительности к регистру имя подпрограммы или функции FOO
должен быть преобразован компилятором в стандартизированный регистр и формат, чтобы он был связан одинаково независимо от регистра. Разные компиляторы реализовали это по-разному, и никакой стандартизации не произошло. Компиляторы Fortran для AIX и HP-UX преобразуют все идентификаторы в нижний регистр. foo
, а компиляторы Fortran Cray и Unicos преобразовывали идентификаторы в верхний регистр. FOO
. Компилятор GNU g77 преобразует идентификаторы в нижний регистр плюс подчеркивание. foo_
, за исключением того, что идентификаторы уже содержат подчеркивание FOO_BAR
добавить два подчеркивания foo_bar__
в соответствии с соглашением, установленным f2c . Многие другие компиляторы, включая от Silicon Graphics (SGI) IRIX компиляторы , GNU Fortran и компилятор Fortran от Intel (кроме Microsoft Windows), преобразуют все идентификаторы в нижний регистр плюс подчеркивание ( foo_
и foo_bar_
, соответственно). В Microsoft Windows компилятор Intel Fortran по умолчанию использует верхний регистр без подчеркивания. [9]
Идентификаторы в модулях Fortran 90 необходимо дополнительно изменить, поскольку одно и то же имя процедуры может встречаться в разных модулях. Поскольку стандарт Fortran 2003 требует, чтобы имена процедур модуля не конфликтовали с другими внешними символами, [10] компиляторы обычно используют имя модуля и имя процедуры с отдельным маркером между ними. Например:
module m
contains
integer function five()
five = 5
end function five
end module m
В этом модуле имя функции будет изменено как __m_MOD_five
(например, GNU Фортран), m_MP_five_
(например, ifort от Intel), m.five_
(например, Sun95 от Oracle) и т. д. Поскольку Фортран не позволяет перегружать имя процедуры, а вместо этого использует общие блоки интерфейса и общие процедуры с привязкой к типу, искаженные имена не должны включать в себя подсказки об аргументах.
Параметр BIND Fortran 2003 отменяет любые изменения имен, выполняемые компилятором, как показано выше .
Ржавчина [ править ]
![]() | Этот раздел нуждается в расширении . Вы можете помочь, добавив к нему . ( июнь 2014 г. ) |
по умолчанию искажаются Имена функций в Rust . Однако это можно отключить с помощью #[no_mangle]
атрибут функции. Этот атрибут можно использовать для экспорта функций в C, C++ или Objective-C . [11] Далее, наряду с #[start]
атрибут функции или #[no_main]
crate, он позволяет пользователю определить точку входа в программу в стиле C. [12]
В Rust используется множество версий схем преобразования символов, которые можно выбрать во время компиляции с помощью -Z symbol-mangling-version
вариант. Определены следующие манглеры:
legacy
Изменение стиля C++ на основе ABI Itanium IA-64 C++. Символы начинаются с_ZN
, а хеши имен файлов используются для устранения неоднозначности. Используется начиная с Rust 1.9. [13]v0
Улучшенная версия устаревшей схемы с изменениями для Rust. Символы начинаются с_R
. Полиморфизм может быть закодирован. Функции не имеют закодированных типов возвращаемых значений (в Rust нет перегрузки). Имена в Юникоде используют модифицированный Punycode . Сжатие (обратная ссылка) использует байтовую адресацию. Используется начиная с Rust 1.37. [14]
Примеры представлены в Rust. symbol-names
тесты. [15]
Objective-C [ править ]
существуют две формы метода По сути, в Objective-C : метод класса («статический») и метод экземпляра . Объявление метода в Objective-C имеет следующую форму:
+ (return-type) name0:parameter0 name1:parameter1 ... – (return-type) name0:parameter0 name1:parameter1 ...
Методы класса обозначаются знаком «+», методы экземпляра — знаком «-». Типичное объявление метода класса может выглядеть так:
+ (id) initWithX: (int) number andY: (int) number;
+ (id) new;
С методами экземпляра, выглядящими так:
- (id) value;
- (id) setValue: (id) new_value;
Каждое из этих объявлений метода имеет определенное внутреннее представление. При компиляции каждый метод именуется согласно следующей схеме для методов класса:
_c_Class_name0_name1_ ...
и это, например, методы:
_i_Class_name0_name1_ ...
Двоеточия в синтаксисе 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
таблицы поиска, функции по существу анонимны.
The SEL
значение селектора не различается между классами. Это обеспечивает полиморфизм .
Среда выполнения Objective-C хранит информацию о типах аргументов и возвращаемых значений методов. Однако эта информация не является частью имени метода и может варьироваться от класса к классу.
Поскольку Objective-C не поддерживает пространства имен , нет необходимости изменять имена классов (которые появляются в виде символов в сгенерированных двоичных файлах).
Свифт [ править ]
Swift хранит метаданные о функциях (и многом другом) в искаженных символах, ссылающихся на них. Эти метаданные включают имя функции, атрибуты, имя модуля, типы параметров, тип возвращаемого значения и многое другое. Например:
Искаженное имя метода func calculate(x: int) -> int
из MyClass
класс в модуле test
является _TFC4test7MyClass9calculatefS0_FT1xSi_Si
, на Свифт 2014 года. Компоненты и их значения следующие: [16]
_T
: префикс для всех символов Swift. Все начнется с этого.F
: Функция без каррирования.C
: Функция класса, т.е. метод4test
: имя модуля с префиксом его длины.7MyClass
: Имя класса, которому принадлежит функция, с префиксом ее длины.9calculate
: имя функции с префиксом ее длины.f
: Атрибут функции. В данном случае «f» означает нормальную функцию.S0
: Обозначает тип первого параметра (а именно экземпляра класса) как первый в стеке типов (здесьMyClass
не является вложенным и поэтому имеет индекс 0)._FT
: начинается список типов кортежа параметров функции.1x
: Внешнее имя первого параметра функции.Si
: указывает встроенный тип Swift.Int для первого параметра._Si
: Тип возвращаемого значения: снова Swift.Int.
Исправление версий, начиная с Swift 4.0, официально задокументировано. Он сохраняет некоторое сходство с Itanium. [17]
См. также [ править ]
- Интерфейс прикладного программирования (API)
- Бинарный интерфейс приложения (ABI)
- Соглашение о вызовах
- Сравнение программного обеспечения для виртуализации приложений (т. е. виртуальных машин)
- Интерфейс внешних функций (FFI)
- Собственный интерфейс Java (JNI)
- Языковая привязка
- Обвязка
- СВИГ
Ссылки [ править ]
- ^ «Двоичный интерфейс приложения» . Дланг.орг . Проверено 19 мая 2020 г.
- ^ Райнер, Шютце (20 декабря 2017 г.). "Новомодное искажение имени Ди " D Блог Получено 1 мая 2020 г.
- ^ Clang — Возможности и цели: совместимость с GCC , 15 апреля 2013 г.
- ^ JBIntel_deleted_06032015. «Различия OBJ между компилятором Intel и компилятором VC» . программное обеспечение.intel.com .
{{cite web}}
: CS1 maint: числовые имена: список авторов ( ссылка ) - ^ «Совместимость MSVC» . Проверено 13 мая 2016 г.
- ^ «Itanium C++ ABI, раздел 5.1. Внешние имена (также известные как искажение)» . Проверено 16 мая 2016 г.
- ^ «Обзор конструкции» . docs.oracle.com .
- ^ «PEP 8 — Руководство по стилю для кода Python» .
- ^ «Краткий обзор проблем смешанного языка» . Руководство пользователя и справочное руководство по компилятору Intel Fortran 15.0 . Корпорация Интел . Проверено 17 ноября 2014 г.
- ^ «Библиотека документации» .
- ^ «Интерфейс внешних функций # Вызов кода Rust из C» . Руководство по ржавчине . www.rust-lang.org . Проверено 13 мая 2016 г.
- ^ «Нет стандартной библиотеки» . Руководство по ржавчине . www.rust-lang.org . Проверено 13 мая 2016 г.
- ^ «rust/src/librustc_codegen_utils/symbol_names/legacy.r.rs по адресу 57e1da59cd0761330b4ea8d47b16340a78eeafa9 · ржавчина-lang/rust · GitHub» . Гитхаб . 3 ноября 2021 г.
- ^ «Искажение символа ржавчины» . Книга Rust RFC .
- ^ «Rust 1.42.0: src/test/ui/symbol-names» . Гитхаб . Проверено 20 марта 2020 г.
- ^ «mikeash.com: Пятничные вопросы и ответы 15 августа 2014 г.: Искажение имен в Swift» . mikeash.com .
- ^ "apple/swift: mangling.rst" . Гитхаб . 3 ноября 2021 г.
Внешние ссылки [ править ]
- Linux Itanium ABI для C++ , включая схему изменения имен.
- Стандартная спецификация Macintosh C/C++ ABI
- c++filt — фильтр для распутывания закодированных символов C++ для компиляторов GNU/Intel.
- undname – инструмент msvc для разбора имен.
- demangler.com — онлайн-инструмент для расчленения символов GCC и MSVC C++.
- Система выполнения Objective-C от Apple. — из языка программирования Objective-C 1.0
- Соглашения о вызовах для разных компиляторов C++ от Агнера Фога содержат подробное описание схем изменения имен для различных компиляторов C++ x86 и x64 (стр. 24–42 в версии от 08.06.2011).
- Искажение/расчленение имен C++ Довольно подробное объяснение схемы искажения имен компилятора Visual C++.
- PHP UnDecorateSymbolName — PHP-скрипт, который распутывает имена функций Microsoft Visual C.
- Смешивание кода C и C++
- Левин, Джон Р. (2000) [октябрь 1999 г.]. «Глава 5: Управление символами» . Линкеры и загрузчики . Серия Моргана Кауфмана по разработке программного обеспечения и программированию (1-е изд.). Сан-Франциско, США: Морган Кауфманн . ISBN 1-55860-496-0 . OCLC 42413382 . Архивировано из оригинала 5 декабря 2012 г. Проверено 12 января 2020 г. Код: [1] [2] Ошибки: [3]
- Фивос Кефалонитис демистифицировал искажение имен