پرش به محتوا

دستکاری نام

از ویکی‌پدیا، دانشنامهٔ آزاد

در ساخت کامپایلر، دستکاری نام (که به آن دکوراسیون نام نیز گفته می‌شود) روشی است که در بسیاری از زبان‌های برنامه‌نویسی جدید و سطح بالا برای حل مشکلات ناشی از نیاز به تعریف کردن اسامی منحصر به فرد در هنگام ترجمه به زبان سطح پایین‌تر استفاده می‌شود.[۱]

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

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

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

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

هدف از دستکاری نام

[ویرایش]

به‌طور کلی می‌توان گفت که امروزه تکنیک‌های دستکاری نام، ۳ هدف اصلی را دنبال می‌کنند[۳] که عبارت‌اند از:

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

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

چگونگی دستکاری نام

[ویرایش]

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

دستکاری نام در زبان‌های 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 و 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 یک طرح دکوراسیون (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 آورده شد. اما زبان‌های دیگری نیز هستند که از این تکنیک استفاده می‌کنند. مانند:

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

جستارهای وابسته

[ویرایش]

منابع

[ویرایش]
  1. ۱٫۰ ۱٫۱ ۱٫۲ ۱٫۳ ۱٫۴ ۱٫۵ ۱٫۶ ۱٫۷ http://www.int0x80.gr/papers/name_mangling.pdf
  2. ۲٫۰ ۲٫۱ ۲٫۲ «IBM Knowledge Center». www.ibm.com (به انگلیسی). دریافت‌شده در ۲۰۱۹-۱۲-۱۷.
  3. ۳٫۰ ۳٫۱ ۳٫۲ ۳٫۳ ۳٫۴ Agner Fog. Calling conventions for different C++ compilers and operating systems.
  4. ۴٫۰۰ ۴٫۰۱ ۴٫۰۲ ۴٫۰۳ ۴٫۰۴ ۴٫۰۵ ۴٫۰۶ ۴٫۰۷ ۴٫۰۸ ۴٫۰۹ ۴٫۱۰ ۴٫۱۱ "Name mangling". Wikipedia (به انگلیسی). 2020-01-15.
  5. 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.
  6. «Name Mangling and extern "C" in C++». GeeksforGeeks (به انگلیسی). ۲۰۱۳-۰۲-۰۳. دریافت‌شده در ۲۰۱۹-۱۲-۱۷.
  7. Martin Reddy, API Design for C++, 2011.
  8. «The Secret Life of C++: Symbol Mangling». web.mit.edu. دریافت‌شده در ۲۰۱۹-۱۲-۱۹.
  9. Clang - Features and Goals: GCC Compatibility, 15 April 2013
  10. "Visual_C++_name_mangling" (به انگلیسی).
  11. «Microsoft C++ Name Mangling Scheme | mearie.org Documents». mearie.org. دریافت‌شده در ۲۰۱۹-۱۲-۱۹.
  12. «C++ ABI for IA-64: Mangling». itanium-cxx-abi.github.io. دریافت‌شده در ۲۰۱۹-۱۲-۱۹.
  13. «IBM Knowledge Center». www.ibm.com (به انگلیسی). دریافت‌شده در ۲۰۲۰-۰۱-۲۴.
  14. «9. Classes — Python 2.7.17 documentation». docs.python.org. دریافت‌شده در ۲۰۲۰-۰۱-۲۴.
  15. «Name mangling». www.freepascal.org. دریافت‌شده در ۲۰۲۰-۰۱-۲۴.
  16. «Name Mangling». www-pnp.physics.ox.ac.uk. دریافت‌شده در ۲۰۲۰-۰۱-۲۴.
  17. «pre-RFC: a new symbol mangling scheme». Rust Internals (به انگلیسی). ۲۰۱۸-۱۰-۰۱. دریافت‌شده در ۲۰۲۰-۰۱-۲۴.
  18. «Name Mangling - an overview | ScienceDirect Topics». www.sciencedirect.com. دریافت‌شده در ۲۰۲۰-۰۱-۲۴.
  19. «The Complete Guide to Function Mangling in iOS». Apteligent (به انگلیسی). دریافت‌شده در ۲۰۲۰-۰۱-۲۴.

پیوند به بیرون

[ویرایش]