Декорація імен ( чи ) — це техніка що використовується компіляторами для перетворення імен функцій, змінних чи інших програмних сутностей в унікальні ідентифікатори.
Метою є добавка додаткової інформації, по суті сигнатури метода для кодування додаткової інформації в назві функції, структури, класу або іншого типу даних, щоб передати більше семантичної інформації від компілятора до компонувальника.
Опис Потреба в декорації імен виникає, коли мова програмування дозволяє іменувати різні сутності однаковим ідентифікатором, наприклад, якщо вони існують в різних просторах імен (зазвичай визначених модулем, класом або явною директивою простору імен) або мають різні сигнатури типів (наприклад, під час перевантаження функцій). В таких випадках потрібно щоб ідентифікатор містив якусь додаткову інформацію, оскільки він може вимагати різних, спеціалізованих правил виклику в машинному коді.
Будь-який об'єктний код, створений компіляторами, зазвичай пов'язується з іншими фрагментами об'єктного коду (створеними тим самим або іншим компілятором) за допомогою програми, який називається лінкером або компонувальником. Компонувальнику потрібна велика кількість інформації про кожну сутність програми. Наприклад, для правильного зв'язування функції компонувальнику потрібно знати не лише її ім'я, а кількість аргументів, їхні типи та їх спосіб передачі тощо.
Прості мови програмування 1970-х років, такі як C, розрізняли підпрограми лише за їхньою назвою, ігноруючи іншу інформацію, включаючи типи параметрів та повернених значень. Передача параметрів також відбувалась по одному стандарту. Пізніші мови програмування, такі як C++, визначили більш строгі вимоги до підпрограм, щоб вони вважалися «рівними», такі як типи параметрів, тип повернення та угода про виклик функцій. Ці вимоги дозволяють перевантажувати методи та виявляти деякі помилки (наприклад, використання різних визначень функції ще під час компіляції різних файлів вихідного коду). Ці суворіші вимоги були необхідні для роботи з існуючими інструментами програмування. Таким чином, додаткові вимоги були закодовані в назві ідентифікатора, оскільки це була єдина інформація, яку мав традиційний лінкер.
Приклади
С Хоча декорація імен зазвичай не є обов'язковою та не використовується мовами програмування, які не підтримують перевантаження функцій, такими як 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
У схемах виклику та функція кодується як_@ та @@ відповідно, де — це кількість байтів (у десятковій системі числення) для аргументу(ів) у списку параметрів (включаючи ті, що передані в регістрах, наприклад, якщо використовується fastcall). У випадку , ім'я функції просто починається з символу підкреслення.
64-розрядна конвенція у Windows (Microsoft C) не має початкового символу підкреслення. Ця різниця може в деяких рідкісних випадках призвести до невирішених зовнішніх проблем під час перенесення такого коду на 64 бітні системи. Наприклад, код Fortran може використовувати «псевдонім» для посилання на метод 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++ функції та змінні, оголошені як static або поміщені в анонімний простір імен) в одиницях трансляції, використовуючи ті ж правила змін, що й для їхніх нестатичних версій. Якщо функції з однаковою назвою (і параметрами для C++) також визначені та використовуються в різних одиницях трансляції, вони також декоруються до однакової назви, що потенційно може призвести до конфлікту. Однак, вони не будуть еквівалентними, якщо їх викликати у їх відповідних модулях трансляції. Компілятори зазвичай можуть вільно видавати довільні зміни для цих функцій, оскільки доступ до них з інших блоків перекладу є нелегальним, тому їм ніколи не знадобиться зв'язування між різними об'єктними областями (таке зв'язування по суті ніколи не потрібне). Щоб запобігти конфліктам зв'язування, компілятори використовуватимуть стандартне декорування, але використовуватимуть так звані «локальні» символи. Під час зв'язування багатьох таких одиниць трансляції може існувати кілька визначень функції з однаковою назвою, але результуючий код викликатиме лише одне з них залежно від того, з якої одиниці трансляції вона походить. Зазвичай це робиться за допомогою механізму переміщення ().
C++ Компілятори C++ вдаються до декорації імен найчастіше через необхідність підтримки різних методів викликів. Перші компілятори C++ були реалізовані як транслятори вихідного коду мови C, який потім компілюється компілятором C в об'єктний код; через це ідентифікатори символів мали відповідати правилам ідентифікаторів C. Навіть пізніше, з появою компіляторів, які безпосередньо створювали машинний код, компонувальник системи зазвичай не підтримував символи C++, і декорування все ще було необхідним.
Мова C++ не визначає стандартної схеми декорації, тому кожен компілятор використовує свою власну. C++ також має складні мовні функції, такі як класи, шаблони, простори імен та можливість перевантаження операторів та функцій; всі ці можливості змінюють значення певних символів залежно від контексту або способу використання. Метадані про ці особливості мають бути добавлені до сигнатури через декорації імен. Оскільки системи декорації імен для таких функцій не стандартизовані між компіляторами, мало хто з лінкерів може лінкувати об'єктний код, створений різними компіляторами.
Простий приклад Один модуль трансляції C++ може визначати дві функції з назвою наступним чином:
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); }
Навіть якщо якесь ім'я унікальне, як то , воно все одно декорується: декорування імен застосовується буквально до всіх символів 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, чим гарантується відсутність конфлікту з ідентифікаторами користувачів); потім йде літера , потім серія пар <довжина, ідентифікатор> (довжина в символах та сама назва ідентифікатора) і, нарешті, літера Наприклад, стає:
_ZN9wikipedia7article6formatE
Для функцій після цього додається інформація про тип параметрів; оскільки є функцією , це просто , отже:
_ZN9wikipedia7article6formatEv
Для використовується стандартний тип (який єВикористовується для ), який має спеціальний псевдонім ; таким чином, посилання на цей тип — , а повна назва функції буде такою:
_ZN9wikipedia7article8print_toERSo
Як різні компілятори декорують одні й ті ж функції Не існує стандартизованої схеми, за якою навіть тривіальні ідентифікатори C++ декоруються, і, як наслідок, різні компілятори (або навіть різні версії одного й того ж компілятора, або один і той самий компілятор на різних платформах) декорують публічні символи радикально різними (а отже, абсолютно несумісними) способами. Розглянемо, як різні компілятори C++ декорують одні й ті ж функції:
Compiler Intel C++ 8.0 for Linux
HP aC++ A.05.55 IA-64 IAR EWARM C++ GCC 3.x and higher Clang 1.x and higher GCC 2.9.x
HP aC++ A.03.45 PA-RISC Microsoft Visual C++ v6-v10 (mangling details)
Digital Mars C++ Borland C++ v3.1 OpenVMS C++ v6.5 (ARM mode) OpenVMS C++ v6.5 (ANSI mode) OpenVMS C++ X7.1 IA-64 SunPro CC Tru64 C++ v6.5 (ARM mode) Tru64 C++ v6.5 (ANSI mode) Watcom C++ 10.6
Примітки:
Компілятор Compaq C++ на OpenVMS VAX та Alpha (але не IA-64) та Tru64 UNIX має дві схеми декорування імен. Оригінальна, достандартна схема відома як модель ARM і базується на стандарті, описаному у C++ Annotated Reference Manual (ARM). З появою нових можливостей у стандартному C++, зокрема шаблонів, схема ARM ставала дедалі менш придатною — вона не могла кодувати певні типи функцій або створювала однаково декоровані назви для різних функцій. Тому її було замінено новою моделлю Американського національного інституту стандартів (ANSI), яка підтримувала всі функції шаблонів ANSI, але не була зворотно сумісною. На IA-64 існує стандартний бінарний інтерфейс програми (ABI) (див. зовнішні посилання), який визначає (серед іншого) стандартну схему декорування імен і який використовується всіма компіляторами IA-64. GNU GCC 3.x також прийняв схему декорування назв, визначену в цьому стандарті, для використання на інших платформах, відмінних від Intel. Visual Studio та Windows SDK містять програму , яка друкує прототип функції у стилі C для заданого задекорованого імені. У Microsoft Windows компілятор Intel та Clang використовують підхід декорування Visual C++ для сумісності.
Обробка символів C під час компонування з C++ Ідея поширеної ідіоми C++:
#ifdef __cplusplus extern "C" { #endif /* ... */ #ifdef __cplusplus } #endif
полягає в тому, щоб забезпечити, щоб символи всередині були «неспотвореними» — щоб компілятор генерував бінарний файл з їхніми іменами без декорування, як це зробив би компілятор C. Оскільки визначення мови C не декоруються, то компілятор C++ ніяк не змінює ці ідентифікатори.
Наприклад, стандартна бібліотека рядків , зазвичай містить щось подібне до:
#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, "-x") == 0) strcpy(a, argv); else memset (a, 0, sizeof(a));
використовує правильні, «неспотворені» назви та . Якби не використовувався, компілятор C++ (SunPro) створив би код, еквівалентний такому: