دستکاری نام
در ساخت کامپایلر، دستکاری نام (که به آن دکوراسیون نام نیز گفته میشود) روشی است که در بسیاری از زبانهای برنامهنویسی جدید و سطح بالا برای حل مشکلات ناشی از نیاز به تعریف کردن اسامی منحصر به فرد در هنگام ترجمه به زبان سطح پایینتر استفاده میشود.
این روش، راهی را برای کدگذاری اطلاعات اضافی در نام یک متغیر، تابع، ساختار، کلاس یا نوعدادهی دیگر ارائه میدهد تا اطلاعات بیشتری از طرف کامپایلرها به پیونددهندهها منتقل شود تا بتوانند اسامی مشترک را از یکدیگر تمیز دهند. چرا که این ویژگی در زبانهای سطح پایینتر (مثلاً اسمبلیها) وجود ندارد و نیاز است که با استفاده از راه دیگری به حل تداخلهای به وجود آمده در نامها پرداخته شود.
نیاز به چنین روشی زمانی به چشم میآید که در یک زبان برنامهنویسی، امکان تعریف موجودیتهای مختلف با شناسهی یکسان فراهم باشد (تا زمانی که فضای نام یا امضاءهای متفاوتی داشته باشند). مثلاً امکان اضافه بار توابع (به انگلیسی: Function Overloading) در برخی زبانهای برنامهنویسی این اجازه را به برنامهنویس میدهد که برای خوانایی بیشتر برنامه یا راحتی کار خودش، توابع مختلفی با نام یکسان داشته باشد.
هر کد هدفی که توسط کامپایلر تولید شده باشد، معمولاً توسط برنامهای به نام پیونددهنده به قطعات دیگری از کد هدف (که ممکن است توسط همین کامپایلر یا کامپایلر دیگری تولید شده باشند) متصل میشود. پیونددهنده برای این کار نیاز به اطلاعات جامعی از هر موجودیت برنامه دارد. برای مثال برای آن که یک تابع را به درستی به کد اصلی متصل کند، نیاز به نام، تعداد پارامترها و نوع پارامترهایش دارد.
هدف از دستکاری نام
بهطور کلی میتوان گفت که امروزه تکنیکهای دستکاری نام، ۳ هدف اصلی را دنبال میکنند که عبارتاند از:
- کمک به پیونددهنده در تفاوت قائل شدن میان موجودیتهایی از برنامه که نام یکسان ولی عملکرد متفاوتی دارند.
- کمک به پیونددهنده در تشخیص دادن این که توابع و شیءها در تمام ماژولها بهطور یکسانی تعریف شده باشند.
- کمک به پیونددهنده در دادن پیغام خطای مناسب در هنگام مواجهه با تداخلهای غیرقابل رفع.
در ابتدا دستکاری نام تنها برای هدف اول ایجاد شد ولی بعدها در کامپایلرها از آن برای اهداف دیگر نیز استفاده شد. به همین دلیل ممکن است برخی کامپایلرها تنها هدف اول را به کمک دستکاری نام انجام دهند و برای انجام دادن دو کار دیگر از روشهای دیگری بهره بگیرند.
چگونگی دستکاری نام
کامپایلرهای مختلف، به روشهای مختلفی این کار را انجام میدهند. حتی در میان کامپایلرهای موجود برای یک زبان خاص نیز لزوماً این روش یکسان نیست. اما در نهایت همهٔ آنها نامها را در زبان مقصد به طریقی تغییر میدهند که به یک مجموعه نام منحصر به فرد برای موجودیتهای برنامه برسند. برای نمونه تبدیل کد توابع را در نظر بگیرید. دو تابع با نام یکسان، مجبورند که امضاءهای متفاوتی داشته باشند (امضای توابع شامل تعداد، نوع و ترتیب ورودیهای و خروجیهایش است). وگرنه نمیتوان آنها را از هم تشخیص داد و برنامه دچار خطای زمان کامپایل میشود. از همین تفاوت میتوان استفاده کرد تا برای توابع نامهای منحصر به فردی تولید کرد.
دستکاری نام در زبانهای C و ++C
به عنوان مثال در برنامهٔ زیر توابعی که نامشان f
است تداخل قابل رفع دارند؛ ولی دو تابع با نام g
تداخلشان قابل رفع نیست:
int f(int a, int b) {
return a + b;
}
int f(int c) {
return c;
}
int f(float a, float b) {
return 1;
}
int g(int a, int b) {
return a + b;
}
int g(int c, int d) {
return 1;
}
اکنون برای توابعی که نامشان f
است، همانطور که گفته شد یک ایدهٔ ساده برای رفع تداخل این است که امضای تابع را درون نامش بگنجانیم؛ مثلاً اولین f
را در نظر بگیرید. خروجیاش از نوع صحیح است و ورودیهایش نیز دو عدد صحیح اند. میتوان قبل از نام تابع نوع خروجیاش را ذکر کرد و پس از آن نیز نوع ورودیهایش را به ترتیب آورد و تمام اینبخشها را با استفاده از زیر خط از هم جدا کرد. به این ترتیب نام اولین تابع به _int_f_int_int
یا به اختصار _i_f_i_i
تغییر میکند. پس از اعمال این کار سه تابع f
بالا به شکل زیر در میآیند:
int _i_f_i_i(int a, int b) {
return a + b;
}
int _i_f_i(int c) {
return c;
}
int _i_f_f_f(float a, float b) {
return 1;
}
و همانطور که مشاهده میکنید دیگر تداخلی در نام توابع وجود ندارد و اسامی کاملاً منحصر به فرد شدهاند.
C
با وجود این که دستکاری نام معمولاً در زبانهایی که از اضافه بار توابع پشتیبانی نمیکنند (مانند C و classic Pascal) نیاز نمیشود، از آنها در برخی کاربردها برای ارائهٔ اطلاعات بیشتر در مورد توابع استفاده میشود. به عنوان مثال کامپایلرهایی که هدفشان اجرا شدن برنامه در پلتفرمهای مایکروسافت ویندوز است، از قراردادهای فراخوانی مختلفی پشتیبانی میکنند که شیوهٔ ارسال پارامترها به زیرروالها و دریافت نتایج برگردانده شده را مشخص میکنند. از آنجا که این قراردادهای فراخوانی با هم سازگاری ندارند، کامپایلرها از دستکاری استفاده میکنند تا بفهمند برای صدا کردن یک زیرروال از کدام قرارداد باید استفاده شود.
روش دستکاری توسط مایکروسافت ایجاد شد و پس از آن به صورت غیررسمی توسط کامپایلرهای دیگری مانند Digital Mars، Borland و GNU GCC در هنگام کامپایل کردن کد برای پلتفرمهای ویندوز استفاده شد. حتی بعدها در زبانهای دیگری مانند Pascal, D, Delphi، Fortran و C# به کار رفت. این موضوع به زیرروالهای نوشته شده در زبانهای مذکور این امکان را میدهد که توسط کتابخانههای ویندوز و با استفاده از قرارداد فراخوانی متفاوتی نسبت به پیشفرض خودشان صدا زده شوند.
هنگام کامپایل کردن مثالهای زیر که به زبان C هستند:
int _cdecl f (int x) { return 0; }
int _stdcall g (int y) { return 0; }
int _fastcall h (int z) { return 0; }
کامپایلرهای ۳۲ بیتی به ترتیب موارد زیر را خروجی میدهند:
_f _g@4 @h@4
در stdcall
و fastcall
که روشهایی برای دستکاری نام هستند، به ترتیب تابع به صورت name@X_
و name@X@
کدگذاری میشود که X تعداد بایتهای ورودی(ها) در لیست پارامترها (شامل آنهایی که در fastcall
در ثباتها پاس داده میشوند) در مبنای ۱۰ است. در حالت cdecl
صرفاً یک زیر خط (_) ابتدای اسم تابع قرار میدهیم.
قرارداد ۶۴ بیتی در ویندوز، در ابتدای اسم زیر خط ندارد. این تفاوت باعث میشود که در برخی مواقع خاص در هنگام تبدیل کردن کد به نسخهٔ ۶۴ بیتی، تعدادی ارجاع حلنشده باقیبماند.
++C
دستکاری نام در کامپایلرهای ++C بسیار استفاده میشود. در استاندارد ++C روش خاصی برای دستکاری نام مشخص نشدهاست بنابراین کامپایلرهای مختلف ممکن است اطلاعات متفاوتی را در اسمها وارد کنند. نمونههای اولیهٔ کامپایلرهای ++C کد منبع را به کد زبان C تبدیل میکردند و سپس آن کد باید به کد هدف که قابل اجرا بر روی ماشین است تبدیل شود. از این جهت نیاز بود تا نمادها با شناسههای C مطابقت داشتهباشند. حتی بعدها که کامپایلرهایی برای ++C نوشته شدند که کد منبع را مستقیماً به کد قابل اجرا بر روی ماشین تبدیل میکردند، پیونددهندههای سیستم عموماً از نمادهای ++C پشتیبانی نمیکردند و در نتیجه دستکاری نام کماکان نیاز بود.
برای زبان ++C یک طرح دکوراسیون (Decoration Scheme) استاندارد وجود ندارد. پس هر کامپایلر مجبور است یک نمونه برای خودش در نظر گرفته و از آن استفاده کند. ++C همچنین شامل ساختارها و ویژگیهای پیچیدهای نظیر کلاسها، قالبها، فضاهای نام و سربارگذاری عملگرها میباشد که معنی یک نماد مشخص را بسته به موقعیتی که در آن استفاده میشود، تغییر میدهد. فراداده در مورد این ویژگیها، با استفاده از دستکاری نام نمادها ممکن است ابهامآمیز باشند. از آنجا که سیستمهای دستکاری نام برای این ویژگیها میان کامپایلرهای مختلف یکسان نیستند، تنها تعداد کمی از پیونددهندهها میتوانند کد شیءای که توسط کامپایلرهای مختلف تولید شدهاند را پیوند دهند.
در ++C نام متغیر یک فضای نام نیز دستکاری میشود به این صورت که اسم فضای نام به آن اضافه میشود تا بتوان متغیرهایی با نام یکسان در فضاهای نام متفاوت داشت. کامپایلرهای ++C اسم متغیرهای C را نیز برای تشخیص فضای نامی که متغیر C در آن قرار دارد دستکاری میکند.
مثالی پیچیدهتر در زبان ++C
در ابتدای این بخش یک مثال سادهٔ فرضی از دستکاری نام در زبان ++C آورده شد. حال به سراغ یک مثال واقعیتر میرویم. نمادهای دستکاری شده در این مثال که در کامنتهایی زیر هر شناسه آورده شدهاند، توسط کامپایلرهای GNU GCC 3.x تولید شدهاند:
namespace wikipedia
{
class article
{
public:
std::string format (void);
/* = _ZN9wikipedia7article6formatEv */
bool print_to (std::ostream&);
/* = _ZN9wikipedia7article8print_toERSo */
class wikilink
{
public:
wikilink (std::string const& name);
/* = _ZN9wikipedia7article8wikilinkC1ERKSs */
};
};
}
تمام نمادهای دستکاری شده با Z_
شروع میشوند (توجه داشته باشید که در زبان C شناسهای که با زیر خط و در ادامه یک حرف بزرگ انگلیسی شروع شود، جزو شناسههای رزرو است، پس از مغایرت میان شناسهها جلوگیری میشود). برای نامهای تو در تو (شامل فضاهای نام و کلاسها)، به دنبالشان یک N
میآید و پس از آن یک دنباله از طولها و شناسهها (منظور طول شناسهٔ بعدی است) قرار میگیرد، و در آخر یک E
. به عنوان مثال، wikipedia ::article::format
تبدیل میشود به:
_ZN9Wikipedia7article6formatE
برای توابع، پس از آن اطلاعات مربوط به نوع ورودیها میآید؛ مثلاً برای format()
که یک تابع void
(بدون ورودی) است به دنبالش v
قرار میگیرد:
_ZN9Wikipedia7article6formatEv
برای print_to
، نوع استاندارد std::ostream
(که یک typedef
برای <<std::basic_ostream<char, std::char_traits<char
است) استفاده شدهاست، که دارای نام مستعار مخصوص So
است؛ پس یک مرجع برای این نوع RSo
است و در نتیجه نام کامل تابع برابر خواهد بود با:
_ZN9Wikipedia7article8print_toERSo
چگونه کامپایلرهای مختلف ++C توابع یکسان را دستکاری میکنند
آنچه در بالا گفته شد تنها مثالی از یک روش برای دستکاری نام بود. در واقعیت، استانداردی برای نحوهٔ دستکاری نامها در زبان ++C وجود ندارد و کامپایلرهای مختلفی که برای آن وجود دارند به روشهای گوناگونی این کار را انجام میدهند. در جدول زیر میتوانید مثالهایی از تعدادی از این روشها مشاهده کنید:
کامپایلر | void h(int)
| void h(int, char)
| void h(void)
|
---|---|---|---|
اینتل C++ 8.0 برای لینوکس | _Z1hi
| _Z1hic
| _Z1hv
|
HP aC++ A.05.55 IA-64 | |||
IAR EWARM C++ 5.4 ARM | |||
GCC 3. x و بالاتر | |||
Clang 1. x و بالاتر | |||
IAR EWARM C++ 7.4 ARM | _Z<number>hi
| _Z<number>hic
| _Z<number>hv
|
GCC 2.9 x | 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
|
++Digital Mars 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
|
مدیریت نمادهای زبان C هنگام پیوند از ++C
زبان C از اضافه بار توابع پشتیبانی نمیکند؛ بنابراین وقتی در کد ++C میخواهیم از توابعی استفاده کنیم که به زبان C نوشتهشدهاند، باید به کامپایلر ++C بگوییم آنها را دستکاری نکند (در صورت دستکاریکردن نام این توابع، پیونددهنده در هنگام پیوند دادن نمیتواند تعریف آنها را بیابد). برای این کار از extern
استفاده میکنیم.
برای مثال، کتابخانه استاندارد رشتهها یعنی <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
به همین دلیل کدی مانند کد زیر از توابع دستکارینشده strcmp
وmemset
استفاده میکند:
if (strcmp(argv[1], "-x") == 0)
strcpy(a, argv[2]);
else
memset(a, 0, sizeof(a));
زبانهای برنامهنویسی دیگری که از دستکاری نام استفاده میکنند
در این مقاله تنها مثالهایی از زبانهای C و ++C آورده شد. اما زبانهای دیگری نیز هستند که از این تکنیک استفاده میکنند. مانند:
در لیست بالا تنها تعدادی از زبانهای معروفی که از این تکنیک استفاده میکنند ذکر شدهاند. بهطور کلی هر زبانی که در آن قابلیت تعریف موجودیتهای مختلف با نامهای یکسان (اضافه بار) وجود داشته باشد میتواند به این لیست اضافه شود.
جستارهای وابسته
منابع
- ↑ http://www.int0x80.gr/papers/name_mangling.pdf
- ↑ «IBM Knowledge Center». www.ibm.com (به انگلیسی). دریافتشده در ۲۰۱۹-۱۲-۱۷.
- ↑ Agner Fog. Calling conventions for different C++ compilers and operating systems.
- ↑ "Name mangling". Wikipedia (به انگلیسی). 2020-01-15.
- ↑ industry, Paul Leahy Paul Leahy is a computer programmer with over a decade of experience working in the IT; in-House, As Both an; Developer, Vendor-Based. "Definition of a Java Method Signature". ThoughtCo (به انگلیسی). Retrieved 2020-01-24.
- ↑ «Name Mangling and extern "C" in C++». GeeksforGeeks (به انگلیسی). ۲۰۱۳-۰۲-۰۳. دریافتشده در ۲۰۱۹-۱۲-۱۷.
- ↑ Martin Reddy, API Design for C++, 2011.
- ↑ «The Secret Life of C++: Symbol Mangling». web.mit.edu. دریافتشده در ۲۰۱۹-۱۲-۱۹.
- ↑ Clang - Features and Goals: GCC Compatibility, 15 April 2013
- ↑ "Visual_C++_name_mangling" (به انگلیسی).
- ↑ «Microsoft C++ Name Mangling Scheme | mearie.org Documents». mearie.org. دریافتشده در ۲۰۱۹-۱۲-۱۹.
- ↑ «C++ ABI for IA-64: Mangling». itanium-cxx-abi.github.io. دریافتشده در ۲۰۱۹-۱۲-۱۹.
- ↑ «IBM Knowledge Center». www.ibm.com (به انگلیسی). دریافتشده در ۲۰۲۰-۰۱-۲۴.
- ↑ «9. Classes — Python 2.7.17 documentation». docs.python.org. دریافتشده در ۲۰۲۰-۰۱-۲۴.
- ↑ «Name mangling». www.freepascal.org. دریافتشده در ۲۰۲۰-۰۱-۲۴.
- ↑ «Name Mangling». www-pnp.physics.ox.ac.uk. دریافتشده در ۲۰۲۰-۰۱-۲۴.
- ↑ «pre-RFC: a new symbol mangling scheme». Rust Internals (به انگلیسی). ۲۰۱۸-۱۰-۰۱. دریافتشده در ۲۰۲۰-۰۱-۲۴.
- ↑ «Name Mangling - an overview | ScienceDirect Topics». www.sciencedirect.com. دریافتشده در ۲۰۲۰-۰۱-۲۴.
- ↑ «The Complete Guide to Function Mangling in iOS». Apteligent (به انگلیسی). دریافتشده در ۲۰۲۰-۰۱-۲۴.
پیوند به بیرون
- Linux Itanium ABI برای ++C - شامل طرحی برای دستکاری نام
- مشخصات استاندارد Macintosh C / C ++ ABI
- C++ filtre - فیلتری که عکس عمل دستکاری نام را برای نمادهای ++C در کامپایلرهای GNU/Intel انجام میدهد
- undname - ابزار msvc برای demangle کردن نامها
- demangler.com - یک ابزار آنلاین برای demangle کردن نمادهای GCC و ++MSVC C
- سیستم زمان اجرا Objective-C - از زبان برنامهنویسی Objective-C اپل 1.0
- قراردادهای فراخوانی برای کامپایلرهای مختلف ++C توسط آگنر مه، شامل توضیحات مفصلی در مورد نقشههای نامشخص برای کامپایلرهای مختلف x86 و x64 ++C است. صفحهٔ ۲۴–۴۲ در نسخه ۲۰۱۱-۰۶-۰۸)
- نام C ++ Mangling / Demangling - توضیحات دقیق در مورد نحوهٔ دستکاری نام کامپایلر ++Visual C
- PHP UnDecorateSymbolName - یک اسکریپت php که نام عملکردهای Microsoft Visual C را دستکاری میکند
- مخلوط کردن کد C و ++C
- مدیریت نماد - «لینک دهندهها و لودرها» توسط جان R. لووین
- mangling نام تغییر یافته توسط Fivos Kefallonitis