رفتن به مطلب
مرجع رسمی سی‌پلاس‌پلاس ایران

cdefender

اساتید
  • تعداد ارسال ها

    4
  • تاریخ عضویت

  • روز های برد

    2

آخرین بار برد cdefender در 6 بهمن 1400

cdefender یکی از رکورد داران بیشترین تعداد پسند مطالب است !

اعتبار در سایت

9 خوب

درباره cdefender

کاربـــر رسمی
مدیران مرجع
  • تاریخ تولد 14 اردیبهشت 1373

اطلاعات شبکه‌ای

  1. بررسی دیزاسمبلی x86 در لینوکس – کامپایلر gcc به منظور بررسی دیزاسمبلی کد منبع C در لینوکس که محتوای آن در تصویر 2 نمایش داده شد، ابتدا خروجی تولید شده توسط کامپایلر gcc برای معماری x86 را توسط دیباگر gdb مورد بررسی قرار خواهیم داد. همانطور که در تصویر 12 قابل مشاهده است، وقتی کامپایلر باینری را با فلگ -ggdb کامپایل می‌کند، به دلیل وجود اطلاعات دیباگ درون باینری، دیباگر gdb می‌تواند اطلاعات تکمیلی و کامل درباره ساختار درونی باینری ELF ما نمایش دهد. برنامه ای که در تصویر 12 برای دیباگ باینری مورد استفاده قرار گرفته است، GDB Dashboard است که علاوه بر کد منبع، کد دیزاسمبلی، اطلاعات مرتبط با پشته و رجیسترها و خروجی که توسط کدها در حال تولید است، نمایش می‌دهد. تصویر 12: خروجی دیزاسمبلی برنامه در محیط لینوکس در ادامه به منظور درک بهتر کدی که توسط gcc برای معماری x86 تولید شده است، خط به خط مورد بررسی و تحلیل قرار خواهیم داد تا با رفتار کامپایلر gcc در تولید کدهای اسمبلی آشنا شویم. همانطور که در تصویر 13 قابل نمایش است، وقتی دستورالعمل call فراخوانی می‌شود، مجدد آدرس دستورالعمل بعدی را درون پشته قرار خواهد داد. آدرسی که توسط call بر روی پشته قرار گرفته است، در تصویر 13 با رنگ قرمز قابل مشاهده است. تصویر 13: خروجی اجرای دستورالعمل call وقتی وارد تابع StructureAnalysis می‌شویم، در گام اول با Prologue تابع روبه‌رو خواهیم شد. در Prologue تابع StructureAnalysis بعد از اینکه فریم جدید تابع ایجاد می‌شود، مقدار 0x424 (1060 دسیمال) از مقدار جاری رجیستر ESP کم خواهد شد. این مقدار نشان می‌دهد که 1060 بایت برای ذخیره‌سازی متغییرهای درون این تابع رزرو خواهد شد. تصویر 14: دیزاسمبی مقداردهی اعضای Structure توسط GCC خروجی که توسط کامپایلر GCC تولید شده است، مشابه خروجی MSVC است. متغیرهای درون Struct با استفاده از آدرس پایه‌ای که درون رجیستر EBP قرار دارد، و همچنین آفست‌هایی که مشخص کننده اندازه متغیرها است، مقداردهی می‌شوند. برای کار با داده‌های اعشاری هم از دستورات fld و fstp استفاده شده است. در نهایت وقتی استراکچر مقداردهی اولیه شد، برای نمایش مقادیر آن‌ها به تابع printf عبور داده می‌شوند. تصویر 15: خروجی دیزاسمبلی تابع StructureAnalysis شایان ذکر است، در ابتدای هر تابع درون برنامه همانطور که در تصویر 15 و تصویر 16 قابل مشاهده است، یک تابع با نام __x86.get_pc_thunk.bx فراخوانی می‌شود. در تجزیه و تحلیل یک باینری لینوکس، تابع __x86.get_pc_thunk.bx معمولاً همراه با ثبت ebx برای محاسبه آدرس پایه یک بخش داده یا کد مستقل از موقعیت (PIC) استفاده می‌شود. هدف __x86.get_pc_thunk.bx این است که آدرس دستور بعدی را در رجیستر ebx بارگذاری کند. این امکان را فراهم می‌کند تا دستورات بعدی بتوانند از ebx به عنوان یک رجیستر پایه استفاده کنند تا به داده‌ها یا کدهای نسبت به موقعیت فعلی در حافظه دسترسی پیدا کنند. با استفاده از __x86.get_pc_thunk.bx و ebx، کدهای مستقل از موقعیت می‌توانند به گونه‌ای نوشته شوند که به آدرس مطلق کد یا داده وابسته نباشند. شایان ذکر است، پیاده‌سازی __x86.get_pc_thunk.bx ممکن است بسته به کامپایلر و توزیع لینوکس متفاوت باشد. این تابع معمولاً توسط کتابخانه‌های اجرایی کامپایلر یا کتابخانه C سیستم ارائه می‌شود. در تصویر 16، این موضوع قابل نمایش است. تصویر 16: نمایش فراخوانی __x86.get_pc_thunk.ax در ابتدای تابع main بررسی دیزاسمبلی x86 در لینوکس – کامپایلر clang خروجی که کامپایلر clang تولید می‌کند به مراتب از خروجی که توسط GCC و MSVC در مراحل قبلی تولید شده است، متفاوتر است. در ادامه خروجی نسخه 14 کامپایلر clang را برای برنامه‌ای که به زبان C نوشته شده بود، مورد بررسی قرار خواهیم داد. در تصویر 17، ساختار دیزاسمبلی تابع main قابل مشاهده است که توسط کامپایلر clang تولید شده است. یکی از مهم ترین تفاوت هایی که در خروجی تولید شده توسط کامپایلر clang نسبت به gcc و msvc وجود دارد، در شروع توابع است. خروجی که clang تولید می‌کند، بعد از ایجاد فریم برای تابع، ما یک دستور call خواهیم داشت. هنگامیکه این دستور call اجرا می‌شود، تازه بدنه اصلی تابع شروع به اجرا خواهد شد. تصویر 17: دیزاسمبلی کد تولید شده توسط clang همانطور که در تصویر 17 قابل مشاهده است، در ابتدای تابع یک call به یک لیبل با عنوان LAB_000112EA داریم که از ابتدای آن اجرای بدنه اصلی تابع شروع می‌شود. تفاوت بعدی که وجود دارد، Calling Convention مورد استفاده توسط کامپایلر clang است که نسبت به msvc و gcc تفاوت دارد. به عنوان مثال، در تصویر 18 اصول عبور پارامترها به تابع printf قابل مشاهده است. در خروجی دیزاسمبلی clang به جای PUSH شدن پارامترها از راست به سمت چپ درون پشته، در خروجی clang از رجیسترها برای عبور پارامترها به توابع C استفاده می‌شود. تصویر 18: نحوه عبور پارامترها به تابع با توجه بررسی خروجی کامپایلرهای GCC و MSVC و Clang در پردازش Structها در زبان سی تفاوت برجسته ای وجود نداشت. هر سه کامپایلر برای مقداردهی اعضای استراکچر از آدرس پایه ای استفاده کرده بودند که توسط رجیستر EBP مورد ارجاع قرار می‌گرفت. دو کامپایلر GCC و MSVC از یک اصول تقریبا مشابه‌ای دنباله روی می کردند، ولی clang به جای استفاده از Stack از رجیسترهای عمومی x86 برای عبور پارامترها به توابع استفاده می‌کرد. پایان.
  2. در تصویر 7، خروجی کپی مقادیر CC به درون آدرسی که توسط EDI مورد ارجاع قرار گرفته است، قابل نمایش است. تصویر 7: کپی مقدار CC به درون آدرس مورد ارجاع توسط EDI دستوراتی که در ادامه آورده شده‌اند، مربوط به فلگ GS کامپایلر هستند که مفهوم قناری در پشته را به درون طرح پشته اضافه می‌کند. ابتدا یک مقدار شبه رندوم که در ثابت __security_cookie قرار دارد توسط کامایلر بازیابی شده و در رجیستر EAX ذخیره می‌شود. سپس بر روی مقدار درون رجیستر EAX با مقدار درون EBP یک عملیات XOR انجام می‌شود و در نهایت، خروجی عملیات XOR در آدرس [ebp-4] ذخیره می‌شود. مقدار نهایی که در این آدرس قرار می‌گیرد، به منظور بررسی عدم تخریب حافظه Stack مورد بررسی قرار خواهد گرفت. در نهایت تابع CheckForDebuggerJustMyCode فراخوانی شده است که به دلیل فعال سازی فلگ JMC به کد اضافه می‌شود. در تصویر 8، خروجی دیزاسمبلی این تابع در حالت Release درون IDA Pro نمایش داده شده است. همانطور که قابل مشاهده است، بخش زیادی از کدهای دیزاسمبلی که در حالت Debug قابل مشاهده است، هنگامیکه باینری در مُد Release تولید می‌شود، آن اطلاعات اضافی وجود ندارد. تصویر 8: خروجی دیزاسمبلی باینری مُد Release درون IDA Pro با این حال، همانطور که در تصویر 9 نمایش داده شده است، بعد از اینکه ما یک Struct تعریف می‌کنیم، و اولین ممبر آن را مقداردهی می‌کنیم، دستور mov dword ptr آورده شده است. هنگامیکه در خروجی لیست دیزاسمبلی با dword رو به رو می‌شویم، به این معنا است که با یک متغیر 32 بیتی رو به رو هستیم. از آنجایی که اولین عضو Struct ما از نوع int بوده است، در اینجا عمل mov بر روی یک آدرس 32 بیتی صورت گرفته است. ولی در ادامه به جای o_structure در سطح اسمبلی کامپایلر از رجیستر ebp به منظور دسترسی به اعضای استراکچر Disassembly استفاده کرده است. مثلا در گام دوم، برای کپی مقدار اسکی 14h به درون عضو m_char از رجیستر ebp به همراه آفستی که آن عضو درون پشته قرار دارد، استفاده کرده است تا مقدار 14h را به درون آن منتقل کند. در ادامه همین مسئله مجدد رخ داده است و تنها اندازه آفست دستورالعمل mov تغییر کرده است. همچنین در نهایت به دلیل اینکه یک مقدار Floating-Point مورد استفاده قرار گرفته است، در سطح اسمبلی شاهد استفاده از رجیسترهای XMM به همراه دستور movss هستیم. هنگامیکه در معماری x86 با یک نوع داده float رو به رو هستیم، از دستورالعمل movss و dword ptr استفاده شده است، اما هنگامیکه با یک نوع داده double رو به رو هستیم، از دستورالعمل movsd و mmword ptr استفاده شده است. تصویر 9: مقداردهی اعضای Struct در زبان C در ادامه آرایه کاراکتری که درون Struct تعریف شده است، با استفاده از تابع strncpy مقداردهی شده است. همانطور که در تصویر 10 قابل مشاهده است، دستور mov esi, esp مقدار جاری پشته را در ثبات esi قرار می‌دهد. این دستور معمولاً برای ذخیره کردن مقدار پشته در یک ثبات استفاده می‌شود. در ادامه دستورالعمل push offset string "Milad" آدرس رشته "Milad" را در پشته قرار می‌دهد. این دستور برای پاس دادن آرگومان‌ها به توابع در Calling Convention زبان C استفاده می‌شود. در ادامه مجدد یک دستور push آورده شده است که این دستور مقدار 400 به صورت عددی را در پشته قرار می‌دهد. دستور lea eax, [ebp-40Ch] آدرس ebp-40C را در eax قرار می‌دهد. دستور lea برای محاسبه آدرس یک متغیر استفاده می‌شود. سپس مجدد مقدار درون این رجیستری با استفاده از دستور push eax در پشته قرار می‌دهد. در نهایت وقتی پارامترهای مورد نیاز strcpy_s به درون پشته کپی شد، این تابع فراخوانی می شود. تابع strcpy_s برای کپی کردن یک رشته به رشته دیگر استفاده می‌شود. دستورالعمل بعدی که فراخوانی شده است add esp, 0Ch مقدار 0Ch (12 در مبنای 16) را به esp اضافه می‌کند. این دستور برای تعیین موقعیت صحیح پشته پس از فراخوانی تابع استفاده می‌شود. در نهایت با فراخوانی دستورالعمل مقدار درون رجیستر esi و esp با دستورالعمل cmp مورد مقایسه قرار خواهند گرفت. این دستور برای بررسی صحت موقعیت پشته استفاده می‌شود. دستورالعمل نهایی که به دلیل فلگ RTC1 به باینری اضافه شده است، تابع __RTC_CheckEsp را فراخوانی می‌کند. این تابع برای بررسی صحت موقعیت پشته استفاده می‌شود. در کل، این کد اسمبلی یک رشته به نام "Milad" را با استفاده از تابع strcpy_s کپی می‌کند و سپس موقعیت پشته را بررسی می‌کند. در تصویر 10، خروجی همین ساختار را در IDA Pro قابل مشاهده است: تصویر 10: خروجی دیزاسمبلی باینری در مُد Release یکی از مهم‌ترین تفاوت‌هایی که در خروجی تولید شده توسط IDA Pro نسبت به خروجی تصویر 9 وجود دارد، مقداردهی تمامی اعضای استراکچر به صورت یکجا است. متغیرها با استفاده از آدرس پایه که توسط رجیستر esp مورد ارجاع قرار گرفته است، و آفست‌هایی که در تصویر 10 قابل مشاهده است، تمامی اعضای استراکچر مقداردهی اولیه شده‌اند. شایان ذکر است، قبل از اینکه تابع strcpy_s فراخوانی شود، پارامترهای آن درون پشته PUSH شده است. شایان ذکر است، دستور and esp, 0FFFFFFC0h اشاره‌گر به پشته یا ESP را تراز می‌کند و دستور sub esp, 440h فضایی برای متغیرهای محلی رزرو می‌کند. این مراحل تضمین می‌کنند که مدیریت صحیح پشته انجام شده و محیط یکنواختی برای اجرای تابع فراهم می‌کنند. سپس در ادامه تمامی این مقادیر با عبور به تابع printf نتیجه آن‌ها برای کاربر نمایش داده خواهند شد. تصویر 11: عبور مقادیر اعضای استراکچر به تابع printf
  3. مقدمه‌ای بر فهمایش دیزاسمبلی در این تحقیق تلاش بنده بر این بوده است که ساختار بلاک‌های اسمبلی که کامپایلر MSVC و GNU و Clang برای استراکچرها زبان C در پلتفرم x86 تولید می‌کنند، مورد بررسی قرار بدهم. شایان ذکر است، خروجی MSVC با استفاده IDA Pro در محیط ویندوز و خروجی GCC و Clang در محیط لینوکس با استفاده از Ghidra مورد بررسی قرار گرفته‌اند. استراکچری که دیزاسمبلی آن مورد بررسی قرار گرفته است، به شرح زیر است: تصویر 1: محتوای استراکچر مورد بررسی در تصویر 1، محتوای استراکچر نمایش داده شده است. این استراکچر شامل 6 عضو از انواع داده در زبان C می‌شود. در تصویر 2، نحوه استفاده از این استراکچر نمایش داده شده است. از قبیل اینکه چگونه اعضای این استراکچر که دارای نوع داده اشاره‌گر به کاراکتر هستند، مقداردهی و مورد فراخوانی قرار گرفته‌اند: تصویر 2: نحوه استفاده از استراکچر در زبان C بعد از اینکه برنامه بالا نوشته شد، برنامه بالا را با فلگ‌های زیر توسط MSVC کامپایل کردم. در جدول 1 فلگ‌های پیش‌فرض که در MSVC تنظیم شده بود، آورده شده است: Debug Configuration Compiler Flags: /JMC /permissive- /ifcOutput "x64\Debug\" /GS /W3 /Zc:wchar_t /ZI /Gm- /Od /sdl /Fd"x64\Debug\vc143.pdb" /Zc:inline /fp:precise /D "_DEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX- /Zc:forScope /RTC1 /Gd /MDd /FC /Fa"x64\Debug\" /EHsc /nologo /Fo"x64\Debug\" /Fp"x64\Debug\Structs.pch" /diagnostics:column Release Configuration Compiler Flags: /permissive- /ifcOutput "x64\Release\" /GS /GL /W3 /Gy /Zc:wchar_t /Zi /Gm- /O2 /sdl /Fd"x64\Release\vc143.pdb" /Zc:inline /fp:precise /D "NDEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX- /Zc:forScope /Gd /Oi /MD /FC /Fa"x64\Release\" /EHsc /nologo /Fo"x64\Release\" /Fp"x64\Release\Structs.pch" /diagnostics:column جدول 1: فلگ‌های MSVC برای کامپایل برنامه برخی از فلگ‌هایی که در مُد Debug و Release برای Visual Studio تعریف شده‌اند، در ادامه مورد تشریح قرار گرفته‌اند. این فلگ ها به صورت پیش فرض در مُدهای مختلف Visual Studio فعال هستند، با این حال فلگ‌های دیگر که به صورت سفارشی به منظور تاثیر آن‌ها بر روی خروجی دیزاسمبلی مورد بررسی قرار خواهند گرفت، به صورت مجزا تشریح خواهند شد: فلگ‌های فعال در مُد دیباگ: فلگ JMC: فلگ Just My Code برای ساده‌تر کردن دیباگ برنامه‌ها طراحی شده است و با تمرکز بر روی کد خود کاربر، کدهای مرتبط با سیستم و فریمورک را مخفی می‌کند. وقتی این ویژگی فعال است، پروسه دیباگ می‌تواند از کد غیرکاربری، مانند کتابخانه‌های سیستم یا فریمورک‌های شخص ثالث عبور کند و این امر باعث می‌شود که مراحل پیمایش و درک جریان کد آسان‌تر شود. به عنوان مثال، هنگامیکه در حال دیباگ برنامه با استفاده از رویکرد Step-In هستیم، وارد توابع سیستمی و کتابخانه‌های جانبی نخواهیم شد. این امر در نهایت موجب می‌شود، رویکرد دیباگ محدود به کدهایی شود که خود برنامه‌نویس نوشته است. فلگ Permissive: فلگ Permissive به منظور حفظ مطابقت با قواعد نحوی و استانداردهای برنامه‌نویسی CPP است. وقتی این فلگ را تنظیم می‌کنید، کامپایلر اصول Compatibility را نادیده خواهد گرفت، و تلاش بر این خواهد شد که طبق استانداردهای جاری مطابقت نحوی (Syntax Conformance) و مطابقت معنایی (Semantic Conformance) برنامه را مورد بررسی قرار بدهد. به عنوان مثال، این گزینه اگر فعال باشد امکان استفاده از توابعی مانند strcpy به ما داده نخواهد شد، و شخص برنامه‌نویس باید از strcpy_s اضافه کند که نسخه ایمن‌سازی شده تابع مذکور است. فلگ GS: فلگ GS به عنوان یک ویژگی امنیتی برای شناسایی برخی از آسیب‌پذیری‌های سرریز بافر استفاده می‌شود. وقتی فلگ GS فعال است، کامپایلر MSVC کد اضافی را به اجرایی تولید شده اضافه می‌کند تا در زمان اجرا بررسی‌هایی روی برخی از عملیات‌های بافر انجام دهد. فلگ GS به حفاظت در برابر سرریز بافرهای مبتنی بر Stack کمک می‌کند. این با افزودن یک کوکی امنیتی به نام Security Cookie به فریم Stack توابعی که از بافرهای لوکال استفاده می‌کنند، عمل می‌کند. این کوکی امنیتی به عنوان یک مقدار نگهبان عمل می‌کند و قبل از بازگشت تابع بررسی می‌شود تا اطمینان حاصل شود که فریم Stack دچار خرابی نشده است. این استفاده از فلگ GS در MSVC باعث ارائه یک لایه اضافی از امنیت برای شناسایی و کاهش آسیب‌پذیری‌های سرریز بافر می‌شود. فلگ ZI: فلگ Zi امکان تولید اطلاعات دیباگینگ را فراهم می‌کند. هنگام استفاده از این فلگ، کامپایلر اطلاعات اضافی را در فایل اجرایی قرار می‌دهد که پروسه دیباگ را بهبود می‌بخشد. فلگ Zi به کامپایلر دستور می‌دهد تا یک فایل PDB تولید کند که شامل سمبول‎ها و اطلاعات دیباگینگ درباره کد منبع است. این اطلاعات شامل نام‌های تابع، نام‌های متغیر، شماره خطوط و سایر جزئیاتی است که برای دیباگ و تجزیه و تحلیل برنامه در زمان اجرا مفید است. فایل PDB که با استفاده از فلگ Zi تولید می‌شود، می‌تواند توسط ابزارهای دیباگینگ مانند Visual Studio Debugger استفاده شود تا دیباگ ساده‌تری را فراهم کند. هنگامیکه یک باینری را در IDA Pro دیباگ می کنیم، اگر فایل PDB آن را به دیباگر بدهیم، خروجی با معناتری را به ما ارائه می‌دهد که صرفا آدرس‌های خالی نیستند. فلگ Od: این فلگ برای غیرفعال‌سازی بهینه‌سازی استفاده می‌شود و به کامپایلر دستور می‌دهد که در طول فرآیند کامپایل، تمام بهینه‌سازی‌ها را غیرفعال کند. زمانی که از فلگ Od استفاده می‌شود، کامپایلر کد اسمبلی بدون بهینه‌سازی تولید می‌کند که به کد منبع اصلی نزدیک است. این مورد در برخی حالت‌ها مفید است، مانند زمانی که برنامه خود را در حالت دیباگ قرار می‌دهید و می‌خواهید کد تولید شده به صورت دقیق با کد منبع اصلی همخوانی داشته باشد تا دیباگ و پیمایش کد آسان‌تر باشد. غیرفعال‌سازی بهینه‌سازی با استفاده از فلگ Od منجر به اجرای کد با سرعت کمتر نسبت به کدهای بهینه‌سازی شده با سایر فلگ‌های بهینه‌سازی مانند O1، O2 یا Ox شود. با این حال، برای اهداف دیباگ می‌تواند مفید باشد زیرا کد تولید شده آسان‌تر قابل فهم است. شایان ذکر است، به طور پیش‌فرض، زمانی که کد منبع را بدون مشخص کردن هیچ فلگ بهینه‌سازی کامپایل می‌کنید، کامپایلر فرض می‌کند Od استفاده شده و بهینه‌سازی را غیرفعال می‌کند. اگر می‌خواهید بهینه‌سازی را فعال کنید، باید یکی از فلگ‌های بهینه‌سازی مانند O1، O2 یا Ox را مشخص کنید تا سطوح مختلف بهینه‌سازی را فعال کنید. فلگ Zc:inline: فلگ Zc برای کامپایلر توابع inline را بر اساس استاندارد زبان CPP فعال یا غیرفعال می‌کند. در زبان CPP، کلیدواژه "inline" برای نشان دادن به کامپایلر استفاده می‌شود که یک تابع باید در محل فراخوانی به صورت inline گسترش یابد، به جای اینکه به صورت یک تابع جداگانه فراخوانی شود. هنگامیکه این فلگ را استفاده می کنیم، تمامی توابعی که به صورت inline تعریف شده اند، در محل فراخوانی خود expand خواهند شد و پرشی به محلی دیگر از حافظه صورت نخواهد گرفت. در نتیجه به دلیل عدم استفاده از حافظه Stack و دستورات مرتبط با کار با پشته سرعت اجرای برنامه بهینه خواهد شد. اگر این فلگ در نظر گرفته نشده باشد، حتی با وجود واژه inline در امضا توابع، کامپایلر توابع مذکور را به صورت inline مورد پردازش قرار نخواهد داد. فلگ fp:precise: فلگ fp:precise در کامپایلر MSVC مدل اعداد ممیز شناور را کنترل می‌کند. به طور پیش فرض، MSVC از گزینه /fp:precise استفاده می‌کند که به معنای تولید کد توسط کامپایلری است که به طور دقیق از استاندارد IEEE 754 پیروی می‌کند. این گزینه کنترل دقیقی را برای حالت گردکردن فراهم می‌کند و تضمین می‌کند که نتایج میانی با دقت کامل محاسبه می‌شوند. فلگ /fp:precise به ویژه در مواردی که نیاز به رعایت دقیق استاندارد IEEE 754 وجود دارد، مانند برنامه‌های علمی یا محاسبات عددی که دقت و سازگاری بسیار مهم است، مفید است. این فلگ می‌تواند سازگاری و قابلیت حمل‌ونقل کدی که بر روی رفتار خاص اعداد ممیز شناور را بستگی دارد، حفظ کند. بنابراین، در مواردی که عملکرد اولویت اصلی است و رعایت دقیق استاندارد لازم نیست، می‌توانید از مدل‌های دیگر اعداد ممیز شناور ارائه شده توسط MSVC مانند /fp:fast یا /fp:strict استفاده کنید. فلگ Zc:forScope: این فلگ قوانین استاندارد CPP را برای متغیرهای حلقه for فعال می‌کند. به طور پیش فرض، MSVC از قوانین قدیمی استفاده می‌کند که متغیر حلقه for در خارج از محدوده حلقه قابل مشاهده است. این موضوع می‌تواند منجر به مشکلات و رفتارهای ناخواسته شود. زمانی که فلگ Zc:forScope فعال شود، قوانین MSVC برای متغیرهای حلقه for مطابق با استاندارد CPP اعمال می‌شود و قابلیت دید متغیر حلقه فقط در بدنه حلقه محدود می‌شود. فلگ RTC1: برای فعال کردن بررسی‌های خطا در زمان اجرا استفاده می‌شود. این فلگ مخفف "Run-Time Error Checks Level 1" می‌باشد. وقتی این فلگ فعال شود، کامپایلر کد اضافی را در برنامه قرار می‌دهد تا بررسی‌هایی در زمان اجرا برای انواع مختلف خطاها انجام دهد، مانند بررسی‌های سرریز بافر، متغیرهای نا‌مقداردهی شده و استفاده نادرست از اشاره‌گرها از جمله این خطاها در برنامه‌نویسی هستند. فعال کردن فلگ RTC1 می‌تواند به شناسایی و تشخیص خطاهای برنامه‌نویسی که ممکن است منجر به رفتار تعریف نشده یا خرابی در زمان اجرا شوند، کمک کند. این فلگ هزینه اضافی را به اجرای برنامه اضافه می‌کند، زیرا بررسی‌های زمان اجرا نیازمند کد و محاسبات اضافی هستند. با این حال، در مراحل توسعه و دیباگ، می‌تواند یک ابزار مفید باشد تا مشکلات احتمالی را در مراحل اولیه شناسایی و رفع کنید. فلگ Gd: این فلگ برای تعیین نحوه فراخوانی توابع C استفاده می‌شود. نحوه فراخوانی تعیین می‌کند که چگونه پارامترهای تابع منتقل می‌شوند و چگونه تابع با کد فراخواننده تعامل می‌کند. وقتی از پرچم /Gd استفاده می‌شود، نحوه فراخوانی پیش‌فرض را مشخص می‌کند که به عنوان نحوه فراخوانی cdecl شناخته می‌شود. در نحوه فراخوانی cdecl، پارامترهای تابع از راست به چپ روی Stack قرار می‌گیرند و فراخواننده مسئول پاکسازی Stack پس از بازگشت تابع است. این نحوه فراخوانی به طور معمول در برنامه‌نویسی به زبان C استفاده می‌شود. شایان ذکر است، به طور پیش‌فرض، MSVC از نحوه فراخوانی cdecl استفاده می‌کند، بنابراین استفاده از فلگ Gd ضروری نیست. با این حال، مشخص کردن صریح /Gd مطمئن می‌شود که نحوه فراخوانی پیش‌فرض استفاده می‌شود. فلگ MDd: فلگ MDd برای تنظیمات دیباگ و استفاده از کتابخانه‌های پیش‌فرض (CRT) استفاده می‌شود. این فلگ برای توسعه و دیباگ برنامه‌ها استفاده می‌شود و به شما امکان می‌دهد تا از امکانات دیباگ موجود در برنامه استفاده کنید. وقتی از فلگ MDd استفاده می‌کنید، برنامه شما با استفاده از کتابخانه‌های پیش‌فرض (CRT) که به صورت دیباگ شده هستند، کامپایل می‌شود. فلگ FC: فلگ FC برای تعیین نام فایل منبع در خروجی کامپایلر استفاده می‌شود. این فلگ با اضافه کردن مسیر کامل فایل منبع به خروجی کامپایلر، به تولید پیام‌های خطا و اخطار با اطلاعات بیشتر کمک می‌کند. هنگام استفاده از فلگ /FC، کامپایلر مسیر کامل فایل منبع را به همراه پیام خطا یا اخطار نمایش می‌دهد. این ویژگی مفید است زمانی که شما چندین فایل منبع در دایرکتوری‌های مختلف دارید، زیرا به شما امکان می‌دهد به سرعت مکان خطا یا اخطار را تشخیص دهید. فلگ FA: فلگ FA برای کنترل تولید کد اسمبلی در فرآیند کامپایل استفاده می‌شود. این فلگ به شما امکان می‌دهد نوع و فرمت کد اسمبلی تولید شده را مشخص کنید. فلگ FA چندین گزینه دارد که رفتار تولید کد اسمبلی را تعیین می‌کنند. فلگ FA در هنگام فرآیند کامپایل، یک فایل اسمبلی (.asm) همراه با فایل آبجکت (.obj) تولید می‌کند. فایل لیستینگ کد اسمبلی شامل کد اسمبلی تولید شده متناظر با کد منبع است. فلگ Fa این گزینه را به ما اجازه می‌دهد نام و مکان سفارشی برای فایل لیستینگ اسمبلی مشخص کنید. شایان ذکر است، فلگ FAs همزمان فایل لیستینگ کد اسمبلی (.asm) و فایل لیستینگ کد ماشین (.cod) را در هنگام کامپایل تولید می‌کند. فایل لیستینگ کد ماشین شامل نمایش شماره‌ای از دستورات ماشین به صورت هگزادسیمال است. فلگ EHsc: این فلگ برای تعیین مدل برخورد با اکسپشن‌ها در کد CPP استفاده می‌شود. این فلگ برای "Exception Handling (Standard C++)" استفاده می‌شود و براساس استاندارد CPP برخورد با اکسپشن‌ها را فعال می‌کند. به طور پیش فرض، MSVC از فلگ EHs استفاده می‌کند که برخورد با اکسپشن‌های CPP را فعال می‌کند اما از مشخص‌کردن نوع اکسپشن پشتیبانی نمی‌کند. هنگام استفاده از فلگ EHsc، رفتار EHs را توسعه می‌دهد تا شامل پشتیبانی از مشخص‌کردن نوع اکسپشن هم باشد. برخورد با اکسپشن یک مکانیزم در CPP است که به شما اجازه می‌دهد اکسپشن‌ها را در طول اجرای برنامه کنترل و انتقال دهید. این مکانیزم روش ساختاری برای مقابله با شرایط استثنایی یا خطاهایی که در زمان اجرا ممکن است رخ دهند، فراهم می‌کند. این فلگ به هر صورت شرایطی را فراهم می‌کند که کامپایلر به صورت خودکار بتواند شرایط خاص را با استفاده از بلاک‌های دستوری try-catch مبتنی بر استاندارد CPP مدیریت و کنترل کند. با این حال، فعال‌سازی این فلگ موجب می‌شود که مقداری بر روی کد Overhead آورده شود و Performance برنامه به خاطر کنترل‌های اضافی که کامپایلر به باینری اضافه می‌کند، کاهش پیدا کند. فلگ‌های فعال در مُد Release: فلگ GL: این فلگ برای فعال کردن Global Optimization استفاده می‌شود. این بهینه‌سازی به کامپایلر امکان می‌دهد تا عملکرد و سرعت اجرای برنامه را بهبود بخشیده و حجم کد تولید شده را کاهش دهد. با استفاده از پرچم GL، کامپایلر MSVC بهینه‌سازی‌هایی را اعمال می‌کند که بهبود عملکرد برنامه را در مقیاس سراسر برنامه فراهم می‌کند. این بهینه‌سازی‌ها می‌توانند شامل تغییرات در ساختار داده‌ها، بهینه‌سازی‌های ریاضی و منطقی، حذف Dead Code و ترتیب اجرای کدها باشند. با فعال کردن فلگ GL، می‌توانید عملکرد برنامه خود را بهبود بخشیده و زمان اجرا را کاهش دهید. همچنین، حجم کد تولید شده نیز کاهش می‌یابد که می‌تواند منجر به افزایش سرعت بارگذاری و اجرای برنامه شود. به طور معمول، برای استفاده کامل از این بهینه‌سازی، باید فلگ LTCG را در مرحله لینک فعال کنید. این بهینه‌سازی‌ها ممکن است باعث افزایش زمان کامپایل شود، اما عملکرد و سرعت اجرای برنامه را بهبود می‌بخشند. فلگ Gy: فلگ Gy برای فعال کردن Function Level Linking استفاده می‌شود. زمانی که در هنگام کامپایل از فلگ Gy استفاده می‌شود، کامپایلر را به تولید فایل‌های آبجکت جداگانه برای هر تابع در کد مجبور می‌کند. این امر به پیوند کارآمدتر و کاهش حجم فایل اجرایی منجر می‌شود. با فعال کردن Function Level Linking با فلگ Gy، لینکر قادر است توابع بی‌استفاده را از فایل اجرایی نهایی حذف کند و حجم کل باینری را کاهش دهد. این قابلیت به خصوص در پروژه‌های بزرگ که نه همه توابع استفاده می‌شوند و نه همه توابع فراخوانی می‌شوند، مفید است. فلگ Gm-: این فلگ به کامپایلر می گوید که تکنیم Incremental Compilation و همچنین استفاده از فایل های PCH را غیرفعال کند. این دو مورد موجب کامپایل برنامه با سرعت بالاتری می شوند اما در برخی شرایط غیرفعال کردن Incremental Compilation و ایجاد فایل .pch می‌تواند مفید باشد. این مورد مفید است زمانی که در پروژه‌های بزرگ کار می‌کنید و هزینه نگهداری و به‌روزرسانی فایل .pch بیشتر از فواید Incremental Compilation سریع است. در حالت کلی، فلگ‌هایی که در بالا آورده شده‌اند به صورت پیش‌فرض در مُدهای Debug و Release در Visual Studio تعریف شده‌اند. برنامه C که با رویکرد Structها نوشته شده است، در هر دو مُد مورد بررسی قرار گرفته است تا درک بهتری از ساختار باینری همچنین با فلگ‌های گوناگون کامپایلر به دست آورده شود. بررسی دیزاسمبلی x86 در ویندوز به منظور درک دیزاسمبلی ساختمان داده Struct در زبان برنامه نویسی C ابتدا خروجی تولید شده برای معماری x86 را در دو مُد Debug و Release مورد بررسی قرار خواهیم داد. بعد از بررسی خروجی دیزاسمبلی برای معماری x86، خروجی کامپایلر MSVC برای معماری x64 را مورد بررسی و ارزیابی قرار خواهیم داد. تصویر 3: کد منبع برنامه مورد بررسی همانطور که در جدول 1 آورده شده است، خروجی دیزاسمبلی کد منبع در تصویر 3 با فلگ‌های پیش‌فرض کامپایلر برای مُد Debug مورد بررسی قرار گرفته است. اولین نکته‌ای در تحلیل کد منبع نظر من را جلب کرد، نادیده گرفتن inline بودن تابع MyOperation بود. با اینکه این تابع را به صورت inline تعریف کرده بودم، و همچنین فلگ Zc:inline هم فعال بود، با این حال کامپایلر تصمیم گرفته است که این تابع را به صورت inline در محل فراخوانی خود گسترش ندهد. از همین روی در تابع main، یک دستور call داریم. وقتی این دستور فراخوانی می‌شود، آدرس 00C11A66 را در پشته قرار می‌دهد تا وقتی دستور ret فراخوانی شد، جریان اجرای باینری به مسیر صحیح خود بازگردد و ادامه اجرای برنامه را بعد از فراخوانی تابع ادامه بدهد. در تصویر 4، خروجی دیزاسمبلی اجرای این دستور در محیط Visual Studio نمایش داده شده است. وقتی دستور Call اجرا شده است، آدرس دستوربعد بر روی بالا پشته قرار گرفته است که با ارجاع به مقدار ESP-4 در پانل Memory می‌توان مقداری که در بالای پشته قرار گرفته است، مشاهده کرد که این مقدار با رنگ آبی نمایش داده شده است. تصویر 4: خروجی اجرای دستور Call در دیباگر Visual Studio در ادامه همین مسئله در دیزاسمبلر IDA Pro مورد بررسی قرار گرفته است. وقتی باینری مذکور را در دیزاسمبلر IDA Pro باز کنیم، و وارد دیباگر این دیزاسمبلر شویم، موقعی که دستور Call فراخوانی شود، آدرس دستور بعدی را بر روی پشته قرار می‌دهد که دستور Ret بتواند فرایند اجرای باینری را به مسیر صحیح خود بازگرداند. در پانل Stack View این باینری مقداری که درون پشته قرار گرفته است، قابل نمایش است. تصویر 5: خروجی اجرای دستور Call در دیباگر IDA Pro در تصویر 5، دیزاسمبلی باینری را در مُد Release درون دیباگر IDA Pro مشاهده می‌کنیم. همانطور که در تصویر 5 قابل مشاهده است، وقتی دستور Call اجرا شده است، آدرس بعدی خود را درون پشته قرار داده است که بعد از اجرای دستورالعمل ret اجرای باینری بتواند به مسیر اجرایی خود بازگردد. بعد از اینکه وارد تابع MyOpertion شویم، خروجی دیزاسمبلی تعریف و مقداردهی Struct در زبان C قابل مشاهده است. در تصویر 6، دیزاسمبلی تابع MyOperation نمایش داده شده است: تصویر 6: خروجی دیزاسمبلی تابع MyOperation سه دستور اول اصطلاحا Prologue تابع MyOperation هستند. این سه دستور موجب ایجاد یک فریم جدید و ایجاد فضا برای ذخیره‌سازی متغیرهای محلی تابع می‌شوند. دستور push ebp موجب ذخیره سازی فریم تابع قبلی بر روی پشته خواهد شد. دستور mov ebp, esp موجب شکل گیری یک فریم جدید برای تابع MyOperation خواهد شد. دستور sub esp, 66Ch موجب کم کردن مقدار 66C از پشته به منظور ذخیره سازی متغیرهای محلی درون تابع خواهد شد. به هر صورت، این دستورالعمل فضایی برای متغیرهای محلی و داده‌های دیگری که توسط تابع نیاز است، در پشته اختصاص می‌دهد. در ادامه سه دستور PUSH داریم که به نظر می آید به این دلیل انجام می‌شوند که قبل از استفاده از رجیسترهای ebx، esi و edi مقادیر قبلی آن‌ها بر روی پشته ذخیره شود تا در ادامه مقادیر آن‌ها قابل بازیابی بانشد. دستور بعدی آدرس مؤثر [ebp-42Ch] را محاسبه کرده و در رجیستر EDI ذخیره می‌کند. دستور بعدی مقدار 10Bh (هگزادسیمال) را به رجیستر ECX منتقل می‌کند. مقدار درون رجیستر ECX به عنوان میزان تکرار برای دستور rep stos است که در گام بعد آورده شده است. دستور rep stos مقداری که درون EAX قرار دارد، به آدرسی که توسط EDI اشاره می کند، کپی خواهد کرد.
  4. هنگامیکه شما برای اولین بار از C به CPP مهاجرت می کنید، یا اصلا برنامه نویسی را قصد دارید با CPP شروع کنید، با مفاهیم متعددی روبرو خواهید شد که شاید برای شما جالب باشند که بدانید، این ایده ها چطور شکل گرفتند، چطور به CPP افزوده شدند و اهمیت آن ها در عمل (هنگام برنامه نویسی و توسعه نرم افزار) چیست. در این پست وبلاگی IOStream، به این خواهیم پرداخت که ایده Overloading و Template و Auto Deduction چطور از CPP سر در آوردند. همانطور که شما ممکن است تجربه کرده باشید، هنگامیکه برنامه نویسی و توسعه نرم افزاری را با C شروع می کنید، برنامه شما چیزی بیش از یک مجموعه بی انتها از توابع و استراکچرها و متغیرها و اشاره گرها و ... نخواهند بود. از همین روی شما مجبور هستید مبتنی بر ایده مهندسی نرم افزار و پارادیم برنامه نویسی ساخت یافته، برای هر کاری یک تابع منحصربفرد پیاده سازی کنید. این تابع باید از هر لحاظی از قبیل نام، نوع ورودی ها، نوع خروجی و حتی نوع عملکرد منحصربفرد باشد تا بتواند یک کار را به شکل صحیح کنترل کند که همین مسئله می تواند در پیاده سازی برخی نرم افزارها، انسان را در جهنم داغ و سوزان قرار بدهد. مثلا پیاده سازی یک برنامه محاسباتی مانند ماشین حساب که ممکن است با انواع داده های محاسباتی مانند عدد صحیح (Integer) و عدد اعشاری (Float) رو به رو شود. از همین روی فرض کنید، ما قرار است یک عمل محاسباتی مانند جمع از برنامه ماشین حساب را پیاده سازی کنیم. برای اینکه برنامه به شکل صحیحی کار کند، باید عمل جمع یا همان Add برای انواع داده های موجود از قبیل عدد صحیح و اعشاری پیاده سازی شود. اگر شما این کار را انجام ندهید، برنامه شما به شکل صحیحی کار نخواهد کرد (یعنی نتایج اشتباه ممکن است برای ما تولید کند). در تصویر زیر، نمونه این برنامه و توابع مرتبط با آن پیاده سازی شده است: #include <stdio.h> int AddInt(int arg_a, int arg_b) { return arg_a + arg_b; } float AddFloat(float arg_a, float arg_b) { return arg_a + arg_b; } double AddDouble(double arg_a, double arg_b) { return arg_a + arg_b; } int main(int argc, const char* argv[]) { int result_int = AddInt(1, 2); float result_float = AddFloat(10.02f, 21.23f); double result_double = AddDouble(9.0, 24.3); printf("Result Integer: %d", result_int); printf("Result Float: %f", result_float); printf("Result Double: %lf", result_double); return 0; } به برنامه بالا دقت کنید. ما سه تا تابع Add با نام های منحصربفرد داریم که سه نوع داده مجزا را به عنوان ورودی دریافت می کنند، سه نوع نتیجه مجزا بازگشت می دهند، اگرچه پیاده سازی آن ها کاملا مشابه هم دیگر است و تفاوتی در پیاده سازی این سه تابع وجود ندارد. ولی به هر صورت، اگر به خروجی دیزاسمبلی برنامه مشاهده کنید، دلیل این مسئله را متوجه خواهید شد که چرا هنگام برنامه نویسی با زبان C، به نام های منحصربفرد نیاز است، چون اگر توابع نام های مشابه با هم داشته باشند، لینکر نمی تواند به دلیل تداخل نام (Name Conflict)، آدرس آن ها را محاسبه یا اصطلاحا Resolve کند. همانطور که در تصویر بالا خروجی دیزاسمبلی برنامه Add را مشاهده می کنید، اگر توابع نام مشابه داشتند، در هنگام فراخوانی (Call) تابع Add تداخل رخ می داد، چون دینامیک لودر سیستم عامل دقیقا نمی داند که کدام تابع را باید فراخوانی کند. برای همین نیاز است وقتی برنامه نوشته می شود، نام توابع در سطح کدهای اسمبلی و ماشین منحصر بفرد باشد. به هر صورت، به نظر شما آیا راهی وجود دارد که ما پیاده سازی این نوع توابع را ساده تر کنیم یا حداقل بار نامگذاری آن ها را از روی دوش توسعه دهنده و برنامه نویس برداریم؟ بله امکان این کار وجود دارد. مهندسان CPP با افزودن ویژگی Overloading و Name Mangling یا همان بحث Decoration مشکل برنامه نویسان در پیاده سازی توابع با نام های منحصربفرد را حل کردند (البته کاربردهای دیگر هم دارد که فعلا برای بحث ما اهمیت ندارند). ویژگی اورلودینگ در CPP به ما اجازه خواهد داد یک تابع با عنوان Add پیاده سازی کنیم که تفاوت آن ها فقط در نوع ورودی و نوع خروجی است. به عنوان مثال، در قسمت زیر، کد برنامه Add را مشاهده می کنید که با قواعد CPP بازنویسی شده است. #include <iostream> int Add(int arg_a, int arg_b) { return arg_a + arg_b; } float Add(float arg_a, float arg_b) { return arg_a + arg_b; } double Add(double arg_a, double arg_b) { return arg_a + arg_b; } int main(int argc, const char* argv[]) { int result_int = Add(1, 2); float result_float = Add(10.02f, 21.23f); double result_double = Add(9.0, 24.3); std::cout << "Result Integer: " << result_int << std::endl; std::cout << "Result Float: " << result_float << std::endl; std::cout << "Result Double: " << result_double << std::endl; return 0; } همانطور که مشاهده می کنید، ما اکنون سه تابع با نام Add داریم. ولی شاید سوال پرسیده شود که چطور لینکر متوجه تفاوت این توابع با یکدیگر می شود درحالیکه هر سه دارای یک نام واحد هستند. اینجاست که مسئله Name Mangling یا همان Decoration نام آبجکت ها در CPP مطرح می شود. اگر شما برنامه مذکور را دیزاسمبل کنید، متوجه تفاوت کد منبع (Source-code) و کد ماشین/اسمبلی (Machine/Assembly-code) خواهید شد. همانطور که در خروجی دیزاسمبلی برنامه اکنون مشاهده می کنید، توابع اگرچه در سطح کد منبع دارای نام مشابه با یکدیگر بودند، اما بعد کامپایل نام آن ها به شکل بالا تبدیل می شود. به این شیوه نام گذاری Name Mangling یا Decoration گویند که قواعد خاصی در هر کامپایلر برای آن وجود دارد. این ویژگی موجب می شود در ادامه لینکر بتواند تمیز بین انواع توابع Add شود. به عنوان مثال، تابع نامگذاری شده با عنوان j__?Add@YAHH@Z تابعی است که به نوعی از تابع Add اشاره دارد که ورودی هایی از نوع عدد صحیح دریافت می کند. این شیوه نامگذاری خلاصه موجب خواهد شد لینکر بتواند به سادگی بین توابع تمایز قائل شود. با این حال هنوز یک مشکل باقی است، و آن هم تکرار مجدد یک پیاده سازی برای هر تابع است. به نظر شما آیا راهی وجود دارد که ما از پیاده سازی مجدد توابعی که ساختار مشابه برای انواع ورودی ها دارند، جلوگیری کنیم؟ باید بگوییم، بله. این امکان برای شما به عنوان توسعه دهنده CPP در نظر گرفته شده است. ویژگی که اکنون به عنوان Templateها در مباحث Metaprogramming یا Generic Programming استفاده می شود، ایجاد شده است تا این مشکل را اساساً برای ما رفع کند. با استفاده از این ویژگی کافی است، طرح یا الگوی یک تابع را پیاده سازی کنید، تا در ادامه خود کامپایلر مبتنی بر ورودی هایی که به الگو عبور می دهید، در Backend، یک نمونه تابع Overload شده مبتنی بر آن الگو برای نوع داده شما ایجاد کند. #include <iostream> template <typename Type> Type Add(Type arg_a, Type arg_b) { return arg_a + arg_b; } int main(int argc, const char* argv[]) { int result_int = Add(1, 2); float result_float = Add(10.02f, 21.23f); double result_double = Add(9.0, 24.3); std::cout << "Result Integer: " << result_int << std::endl; std::cout << "Result Float: " << result_float << std::endl; std::cout << "Result Double: " << result_double << std::endl; return 0; } به عنوان مثال، در بالا تابع Add را مشاهده می کنید که نوع داده ورودی این تابع و حتی نوع خروجی آن مشخص نشده است و در قالب Typename به کامپایلر معرفی شده است. این یک الگو برای تابع Add است. کامپایلر اکنون می تواند مبتنی بر ورودی هایی که به تابع هنگام فراخوانی یا اصطلاحا Initialization عبور می دهیم، یک نمونه تابع Overload شده از آن الگو ایجاد کند و در ادامه آن را برای استفاده در محیط Runtime فراخوانی کند. حال اگر برنامه بالا را دیزاسمبل کنید، مشاهده خواهید کرد که کامپایلر از همان قاعده Overloading استفاده کرده است تا نمونه ای از تابع Add متناسب با نوع ورودی هایش ایجاد کند. هنوز می توان برنامه نویسی با CPP را جذاب تر و البته ساده تر کرد، اما چطور؟ همانطور که در قطعه کد بالا مشاهده می کنید، هنوز ما باید خود تشخیص دهیم که نوع خروجی تابع قرار است به چه شکل باشد. این مورد خیلی مواقع مشکل ساز خواهد بود. برای حل این مسئله، در CPP مبحثی در نظر گرفته شده است که آن را به عنوان Auto Deduction می شناسیم که سطح هوشمندی کامپایلر CPP را بالاتر می برد. در این ویژگی خود کامپایلر است که مشخص می کند نوع یک متغیر مبتنی بر خروجی که به آن تخصیص داده می شود، چیست. به عنوان مثال، شما می توانید برنامه بالا را به شکل زیر بازنویسی کنید: #include <iostream> template <typename Type> auto Add(Type arg_a, Type arg_b) { return arg_a + arg_b; } int main(int argc, const char* argv[]) { auto result_int = Add(1, 2); auto result_float = Add(10.02f, 21.23f); auto result_double = Add(9.0, 24.3); std::cout << "Result Integer: " << result_int << std::endl; std::cout << "Result Float: " << result_float << std::endl; std::cout << "Result Double: " << result_double << std::endl; return 0; } با استفاده از ویژگی Auto Deduction و کلیدواژه Auto در برنامه، خود کامپایلر در ادامه مشخص خواهد کرد که تابع Add چه نوع خروجی دارد و همچنین نوع متغیرها برای ذخیره سازی خروجی Add چه باید باشد. به عبارتی اکنون تابع Add هم Value و هم Data type را مشخص می کند که این موجب می شود برنامه نویسی با CPP خیلی ساده تر از گذشته شود. حال اگر به نمونه برنامه آخر نگاه کنید و آن را با نمونه C مقایسه کنید، متوجه خواهید شد که CPP چقدر کار را برای ما ساده تر کرده است. در این پست به هر صورت، قصد داشتم به شما نشان دهم که نحوه تحول CPP به صورت گام به گام چطور بوده است و البته اینکه پشت هر ویژگی در CPP چه منطق کلی وجود دارد. امیدوارم این مقاله برای شما مفید بوده باشد. نمونه انگلیسی این مقاله را می توانید در این آدرس (لینک) مطالعه کنید. میلاد کهساری الهادی
×
×
  • جدید...