دستکاری نام

در ساخت کامپایلر، دستکاری نام (که به آن دکوراسیون نام نیز گفته می‌شود) روشی است که در بسیاری از زبان‌های برنامه‌نویسی جدید و سطح بالا برای حل مشکلات ناشی از نیاز به تعریف کردن اسامی منحصر به فرد در هنگام ترجمه به زبان سطح پایین‌تر استفاده می‌شود.http://www.int0x80.gr/papers/name_mangling.pdf

این روش، راهی را برای کدگذاری اطلاعات اضافی در نام یک متغیر، تابع، ساختار، کلاس یا نوع‌دادهی دیگر ارائه می‌دهد تا اطلاعات بیشتری از طرف کامپایلرها به پیونددهنده‌ها منتقل شود تا بتوانند اسامی مشترک را از یکدیگر تمیز دهند. چرا که این ویژگی در زبان‌های سطح پایین‌تر (مثلاً اسمبلیها) وجود ندارد و نیاز است که با استفاده از راه دیگری به حل تداخل‌های به وجود آمده در نام‌ها پرداخته شود.

نیاز به چنین روشی زمانی به چشم می‌آید که در یک زبان برنامه‌نویسی، امکان تعریف موجودیت‌های مختلف با شناسه‌ی یکسان فراهم باشد (تا زمانی که فضای نام یا امضاءهای متفاوتی داشته باشند). مثلاً امکان اضافه بار توابع (به انگلیسی: Function Overloading) در برخی زبان‌های برنامه‌نویسی این اجازه را به برنامه‌نویس می‌دهد که برای خوانایی بیشتر برنامه یا راحتی کار خودش، توابع مختلفی با نام یکسان داشته باشد.

هر کد هدفی که توسط کامپایلر تولید شده باشد، معمولاً توسط برنامه‌ای به نام پیونددهنده به قطعات دیگری از کد هدف (که ممکن است توسط همین کامپایلر یا کامپایلر دیگری تولید شده باشند) متصل می‌شود. پیونددهنده برای این کار نیاز به اطلاعات جامعی از هر موجودیت برنامه دارد. برای مثال برای آن که یک تابع را به درستی به کد اصلی متصل کند، نیاز به نام، تعداد پارامترها و نوع پارامترهایش دارد.

بندانگشتی|420px|نمایی از تغییرات اعمال شده بر روی نام چند تابع توسط کامپایلر GCC 8.3 با تکنیک دستکاری نام

 هدف از دستکاری نام 
به‌طور کلی می‌توان گفت که امروزه تکنیک‌های دستکاری نام، ۳ هدف اصلی را دنبال می‌کنند که عبارت‌اند از:
 کمک به پیونددهنده در تفاوت قائل شدن میان موجودیت‌هایی از برنامه که نام یکسان ولی عملکرد متفاوتی دارند.
 کمک به پیونددهنده در تشخیص دادن این که توابع و شیءها در تمام ماژول‌ها به‌طور یکسانی تعریف شده باشند.
 کمک به پیونددهنده در دادن پیغام خطای مناسب در هنگام مواجهه با تداخل‌های غیرقابل رفع.

در ابتدا دستکاری نام تنها برای هدف اول ایجاد شد ولی بعدها در کامپایلرها از آن برای اهداف دیگر نیز استفاده شد. به همین دلیل ممکن است برخی کامپایلرها تنها هدف اول را به کمک دستکاری نام انجام دهند و برای انجام دادن دو کار دیگر از روش‌های دیگری بهره بگیرند.

 چگونگی دستکاری نام 
کامپایلرهای مختلف، به روش‌های مختلفی این کار را انجام می‌دهند. حتی در میان کامپایلرهای موجود برای یک زبان خاص نیز لزوماً این روش یکسان نیست. اما در نهایت همهٔ آنها نام‌ها را در زبان مقصد به طریقی تغییر می‌دهند که به یک مجموعه نام منحصر به فرد برای موجودیت‌های برنامه برسند. برای نمونه تبدیل کد توابع را در نظر بگیرید. دو تابع با نام یکسان، مجبورند که امضاءهای متفاوتی داشته باشند (امضای توابع شامل تعداد، نوع و ترتیب ورودی‌های و خروجی‌هایش است). وگرنه نمی‌توان آنها را از هم تشخیص داد و برنامه دچار خطای زمان کامپایل می‌شود. از همین تفاوت می‌توان استفاده کرد تا برای توابع نام‌های منحصر به فردی تولید کرد.

 دستکاری نام در زبان‌های C و ++C 
به عنوان مثال در برنامهٔ زیر توابعی که نامشان  است تداخل قابل رفع دارند؛ ولی دو تابع با نام  تداخلشان قابل رفع نیست: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;
}اکنون برای توابعی که نامشان  است، همان‌طور که گفته شد یک ایدهٔ ساده برای رفع تداخل این است که امضای تابع را درون نامش بگنجانیم؛ مثلاً اولین  را در نظر بگیرید. خروجی‌اش از نوع صحیح است و ورودی‌هایش نیز دو عدد صحیح اند. می‌توان قبل از نام تابع نوع خروجی‌اش را ذکر کرد و پس از آن نیز نوع ورودی‌هایش را به ترتیب آورد و تمام این‌بخش‌ها را با استفاده از زیر خط از هم جدا کرد. به این ترتیب نام اولین تابع به  یا به اختصار  تغییر می‌کند. پس از اعمال این کار سه تابع  بالا به شکل زیر در می‌آیند: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

در  و  که روش‌هایی برای دستکاری نام هستند، به ترتیب تابع به صورت @_ و @@ کدگذاری می‌شود که  تعداد بایت‌های ورودی(ها) در لیست پارامترها (شامل آن‌هایی که در  در ثبات‌ها پاس داده می‌شوند) در مبنای ۱۰ است. در حالت  صرفاً یک زیر خط (_) ابتدای اسم تابع قرار می‌دهیم.

قرارداد ۶۴ بیتی در ویندوز، در ابتدای اسم زیر خط ندارد. این تفاوت باعث می‌شود که در برخی مواقع خاص در هنگام تبدیل کردن کد به نسخهٔ ۶۴ بیتی، تعدادی ارجاع حل‌نشده باقی‌بماند.

 ++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. به عنوان مثال،  ::article::format تبدیل می‌شود به:

 _ZN9Wikipedia7article6formatE

برای توابع، پس از آن اطلاعات مربوط به نوع ورودی‌ها می‌آید؛ مثلاً برای  که یک تابع  (بدون ورودی) است به دنبالش v قرار می‌گیرد:

 _ZN9Wikipedia7article6formatEv

برای ، نوع استاندارد  (که یک  برای <<std::basic_ostream<char, std::char_traits<char است) استفاده شده‌است، که دارای نام مستعار مخصوص So است؛ پس یک مرجع برای این نوع RSo است و در نتیجه نام کامل تابع برابر خواهد بود با:

 _ZN9Wikipedia7article8print_toERSo

 چگونه کامپایلرهای مختلف ++C توابع یکسان را دستکاری می‌کنند 
آنچه در بالا گفته شد تنها مثالی از یک روش برای دستکاری نام بود. در واقعیت، استانداردی برای نحوهٔ دستکاری نام‌ها در زبان ++C وجود ندارد و کامپایلرهای مختلفی که برای آن وجود دارند به روش‌های گوناگونی این کار را انجام می‌دهند. در جدول زیر می‌توانید مثال‌هایی از تعدادی از این روش‌ها مشاهده کنید:
 کامپایلر اینتل C++ 8.0 برای لینوکس    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    GCC 2.9 x    HP aC++ A.03.45 PA-RISC Microsoft Visual C++ v6-v10    ++Digital Mars C Borland C++ v3.1    OpenVMS C++ V6.5 (حالت ARM)    OpenVMS C++ V6.5 (حالت ANSI)   OpenVMS C++ X7.1 IA-64    SunPro CC    Tru64 C++ V6.5 (حالت ARM)    Tru64 C++ V6.5 (حالت ANSI)    Watcom C++ 10.6   

 مدیریت نمادهای زبان C هنگام پیوند از ++C 
زبان C از اضافه بار توابع پشتیبانی نمی‌کند؛ بنابراین وقتی در کد ++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[1], "-x") == 0)
    strcpy(a, argv[2]);
else
    memset(a, 0, sizeof(a));

 زبان‌های برنامه‌نویسی دیگری که از دستکاری نام استفاده می‌کنند 
در این مقاله تنها مثال‌هایی از زبان‌های C و ++C آورده شد. اما زبان‌های دیگری نیز هستند که از این تکنیک استفاده می‌کنند. مانند:
 Java
 Python
 Pascal
 Fortran
 Rust
 Objective-C
 Swift

در لیست بالا تنها تعدادی از زبان‌های معروفی که از این تکنیک استفاده می‌کنند ذکر شده‌اند. به‌طور کلی هر زبانی که در آن قابلیت تعریف موجودیت‌های مختلف با نام‌های یکسان (اضافه بار) وجود داشته باشد می‌تواند به این لیست اضافه شود.

 جستارهای وابسته 
 اضافه بار توابع
 کامپایلر
 قرارداد فراخوانی
 فراخوانی توابع خارجی
 قید (رایانه)
 Stropping
 ++C

 منابع 

 پیوند به بیرون 
 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

رده:برنامه‌نویسی تابعی
رده:برنامه‌نویسی شیءگرا
رده:برنامه‌نویسی
رده:جاوا (زبان برنامه‌نویسی)
رده:سی++
رده:کامپایلرسازی
رده:کامپایلرها
رده:کتابخانه‌های رایانه
رده:کلاس (برنامه‌نویسی)
رده:نرم‌افزار