Press "Enter" to skip to content

نکات مهمی که هنگام استفاده از ++C/C باید به خاطر داشته باشید

زبان‌های برنامه‌نویسی C و ++C از جمله زبان‌های قدیمی، بسیار قدرتمند و پرکاربرد هستند. این زبان‌ها در زمینه‌های مختلفی، از جمله برنامه‌نویسی سطح پایین، سیستم عامل، بازی‌سازی، هوش مصنوعی، رباتیک، تولید اپلیکیشن و حتی وب، مورد توجه توسعه دهندگان قرار دارند. با وجود اینکه زبان‌ها عموما به عنوان زبان‌های قدیمی و مشکل شناخته می‌شوند، در نظرسنجی سال 2024 که توسط Stack Overflow انجام شد در میان محبوب‌ترین زبان‌های برنامه‌نویسی در جایگاه 9 و 10 قرار گرفتند. اما این زیان‌ها چه مزایایی دارند که هنوز در لیست زبان‌های محبوب و پرکاربرد قرار دارند

مزایای ++C/C

کنترل کامل روی سخت‌افزار و منابع سیستم

این زبان‌ها به توسعه‌دهنده این امکان را می‌دهند که مستقیما به حافظه، پردازنده و سایر منابع سخت‌افزاری دسترسی داشته باشند، که باعث می‌شود برای برنامه‌نویسی سیستم عامل، درایورها و ابزارهای سطح پایین بسیار مناسب باشند.

سیستم عامل‌ها: کرنل لینوکس (با استفاده از C) – خانواده ویندوز‌های NT (با استفاده از C و ++C) – کرنل XNU macOS (با استفاده از C و ++C و Objective-C)، FreeBSD (با استفاده از C) – Android (با استفاده از C و ++C و Java) – iOS (با استفاده از C و ++C و Objective-C)

درایورها: تقریبا تمام درایورهای لینوکس با C نوشته شده‌اند – برای درایورهای خانواده ویندوز‌های NT بیشتر از C و به طور محدود از ++C استفاده شده است – برای درایورهای macOS و iOS از C و ++C و Objective-C به صورت مستقیم و غیر مستقیم استفاده شده است – درایورهای Android با C و ++C نوشته شده‌اند.

ابزارهای سطح پایین: GRUB (با استفاده از C و Assembly) – ‌BIOS (با استفاده از C و Assembly) – اکثر کامپایلرها (با استفاده از C و ++C)

کارایی بالا (High Performance)

اگر برنامه‌ای که با ++C/C می‌نویسید به درستی بهینه‌سازی شود، سرعت و کارایی بسیار بالا دارد. در بسیاری از جداول و نمودارهای بنچمارک (Benchmark) که در سایت‌های مختلف قرار دارند این دو زبان، اگر در جایگاه‌های اول نباشند، قطعا در خانه‌های بالایی قرار دارند. به همین دلیل در زمینه‌هایی مثل بازی‌سازی، موتورهای گرافیکی، شبیه‌سازی‌های علمی و نرم‌افزارهای بلادرنگ (بی‌درنگ‌، real-time) کاربرد دارند.

بازی‌ها: DOOM (1993) – Quake – Half-Life – The Witcher – Elder Scrolls V: Skyrim – Fortnite

موتورهای گرافیکی: Unreal Engine – Unity Engine – Godot Engine – CryEngine – id Tech – Vulcan – DirectX – OpenGL

شبیه‌سازهای علمی: LAMMPS – ANSYS Fluent – Abaqus – Simulink – MATLAB Simulations – Bullet – PhysX

قابل استفاده در سیستم‌های تعبیه‌شده (Embedded Systems)

به دلیل مصرف کم منابع وکنترل کامل روی سخت‌فزار، این دو زبان گزینه‌های ایده‌آل برای توسعه نرم‌افرازها در دستگاه‌های الگترونیکی و میکروکنترلر‌ها، مانند برد‌های آردوینو (Arduino)،ESP، STM32  و …، هستند.

سیستم‌های تعبیه شده: Arduino SDk – Expressif – MicroPython

پیچیدگی‌ها و نکاتی که باید در ++C/C در نظر داشت اما در کنار این مزایا پیچیدگی‌هایی وجود دارند، که بی‌توجهی به آنها عواقب وخیمی دارد. عمده مشکلات و سختی‌های استفاده از این زبان‌ها ناشی از این پیچیدگی‌هاست.

پیچیدگی‌ها و نکاتی که باید در ++C/C در نظر داشت

مدیریت دستی حافظه (Manual Memory Management)

در C و ++C خبری از مدیریت خودکار حافظه (مانند grabage collector) نیست. پس مدیریت حافظه باید به صورت دستی توسط برنامه نویس انجام شود؛ یعنی توسعه‌دهنده باید خودش حافظه را رزرو (allocate) کند (مثلا با malloc در C یا new در ++C) و بعد از پایان کار باید حافظه را آزاد (free) کند (مانند free در C یا delete در ++C). این موضوع مانند یک شمشیر دو لبه عمل می‌کند: از طرفی امتیازاتی مانند کارایی (Performance)، قابلیت کنترل دقیق و  در نتیجه مناسب بودن زبان برای توسعه سیستم عامل، درایور و سیستم‌های تعبیه‌شده (Embedded) دارد، اما در نقطه مقابل مشکلاتی مانند پیچیده شدن برنامه‌نویسی، خطر نشت حافظه (memory leak)، خطر استفاده نادرست از حافظه و سخت و زمان‌بر شدن عیب‌یابی (debugging) ایجاد می‌کند.

رفتار تعریف نشده (Undefined Behavior, UB)

در زبان‌های C و ++C اگر برنامه‌نویس کاری انجام دهد که براساس استاندارد زبان تعریفی برای آن وجود نداشته باشد، نتیجه آن می‌تواند کاملا غیرقابل پیش‌بینی باشد. به این اتفاق رفتار تعریف نشده می‌گویند. به عنوان مثال تقسیم عدد صحیح بر صفر، استفاده از اشاره‌گر (pointer) بدون مقداردهی اولیه یا دسترسی به آرایه خارج از محدوده.

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

کمبود سازوکارهای ایمن‌سازی (Lack of Safety Features)

زبان‌های C و ++C حفاظت زیادی در برابر اشتباهات رایج ارایه نمی‌کنند. نمونه این اشتباهات در موارد قبل ذکر شد؛ مثلا اینکه بررسی خودکار برای دسترسی خارج از محدوده آرایه وجود ندارد یا اینکه برسی نوع (type) در زمان اجرا (run-time) انجام نمی‌شود. حتی ارجاع به اشاره‌گر تهی (null pointer) بدون هیچ هشداری باعت کرش برنامه می‌شود.

دستور زبان پیچیده و غیر منسجم به خصوص در ++C (Complex and Inconsistent Syntax)

در زبان ++C، به دلیل ویژگی‌ها و تغییرات تاریخی زیاد آن، دستور زبان (syntax) بسیار پیچیده است، طوری که به نظر می‌آید بخش‌های مختلف زبان هر کدام به دوره متفاوتی از تاریخ علوم کامپوتر تعلق دارند؛ همچنین گاهی اوقات الگوها یا ساختارهای مشابه رفتار متفاوتی دارند و با هم ناسازگار هستند. مثلا راه‌های مختلفی برای تعریف توابع، کلاس‌ها، قالب‌ها (templates) یا متغییرها وجود دارد که ممکن است به نظر غیر منسجم یا گیج کننده باشند. همچنین برخی عبارات شبیه به هم هستند ولی تفاوت جزیی در معنا یا عمکرد دارند. یا اینکه  ممکن است راه‌های متعددی برای انجام یک کار وجود داشته باشد، که البته بعضی خطرناک و بعضی امن هستند.

هم‌زمانی چالش برانگیز است (Concurrency is Hard)

هم‌زمانی (پردازش هم‌زمان، concurrency) به معنای اجرای چند عملیات یا پردازش به طور هم‌زمان یا در تداخل با یکدیگر است، که در برنامه‌نویسی برای استفاده بهینه از منابع (مثلا هسته‌های مختلف پردازنده) اهمیت زیادی دارد. به طور کلی همزمانی در برنامه‌نویسی دشوار است و این موضوع محدود به C و ++C نمی‌شود. 

مبحث  همزمانی همیشه یکی از مسایل مهم در برنامه‌نویسی است، چرا که هماهنگ کردن چند عملیات هم‌زمان به گونه‌ای که داده‌ها درست رد وبدل شوند و مفاهیمی مثل قفل‌ها (locks)، رشته‌های اجرایی (threads)، وضعیت‌های رقابتی (race condition) و بن‌بست (deadlock) چالش برانگیز نشوند، کار مشکلی‌ست.

مشکلات مربوط به سازگاری (Compatibility Issues)

کد ممکن است در یک سیستم (مثلا ویندوز یا لینوکس)، کامپایلر (مثلا GCC یا MSVC) یا استاندارد (مثلا 17++C یا 11++C) به خوبی اجرا شود، اما در دیگری خطا بدهد یا رفتار متفاوتی داشته باشد.

مشکلات مربوط به ابزارها (Tooling Issues)

ابراز‌های کمکی، مانند فرمت‌کننده کد (code formatter)، پروفایلر (profiler)، دیباگر (debugger) یا استاتیک آنالایزر (static analyzer)، برای ++C/C محدود و ضعیف هستند یا کاربری پیچیده دارند، به خصوص عیب‌یابی حافظه که سخت و زمانبر است. به علاوه نه تنها پیام‌های خطای برخی کامپایلر‌ها مبهم و گیج کننده است، زمان کامپایل می‌تواند طولانی باشد و فایل‌های هدر (header files) باعث رشد تصاعدی حجم برنامه ‌می‌شوند.

سیستم‌های ساخت (build systems) پیچیده، پراکنده و گاهی ناسازگارند، پس یک پروژه ساده ممکن است نیاز به تنظیمات پیچیده داشته باشد. مدیریت وابستگی‌ها (dependencies) باید معمولا دستی انجام شود. هر چند پکیج منجرهایی (package manager) مانند vcpkg یا conan وجود دارند، اما هیچ‌کدام رسمی نیستند و با محدودیت‌های زیادی همراه‌اند. همچنین پشتیبانی خوبی از این زبان‌ها در ابزارهای DevOps و CI/CD، به دلیل وابستگی‌های بومی و پیچیدگی پلتفرم، ارایه نمی‌شود. 

همچنین محیط‌های توسعه (IDE) بالغ و قدرتمندی مشابه بعضی زبان‌های برنامه نویسی دیگر وجود ندارد. البته IDEهای حرفه‌ای مانند Clion و Visual Studio وجود دارند، ولی هردو سنگین و تجاری هستند و راه‌اندازی آن‌ها برای پروژه‌های کراس-پلتفرم (cross-platform) کمی سخت است.

ریسک‌های امنیتی (Security Risks)

ریسک‌های امنیتی به خطراتی اشاره دارد که می‌تواند باعث نقض امنیت یک نرم‌افزار یا سیستم شوند؛ مثلا نفوذ مهاجمان به سیستم، افشای داده‌های حساس، دستکاری داده‌ها یا حتی از کار انداختن سرویس.

این ریسک‌ها در C و ++C اهمیت بیشتری دارند، چرا که امکان دسترسی مستقیم به حافظه وجود دارد و بررسی‌های محافظتی انجام نمی‌شود، در نتیجه مثلا خطاهایی مانند use-after-free، buffer overflow یا null pointer dereference می‌توانند منجر به نفوذ یا کرش سیستم شوند.

چالش‌ها و مشکلات کتابخانه استاندارد ++C/C (Standard Library Pitfalls)

در ++C/C این مشکل معمولا به دلیل توابع قدیمی وناامن در کتبخانه استاندارد، رفتارهای غیر شفاف یا پیچیده در برخی توابع استاندارد، تفاوت در پیاده‌سازی بین پلتفرم‌ها با کامپایلر‌ها و مستندسازی ناکافی یا نکات پنهان پیش می‌آیند

توجه: لیست بالا ممکن است به نظر طولانی باشد، اما چند نکته مهم در ارتباط با این لیست را در نظر داشته باشید:

  • با وجود اینکه موارد متعددی در این لیست وجود دارد، اما با کمی دقت متوجه می‌شوید که بعضی از این موارد ریشه مشترکی دارند مثلا عمده موارد ذکر شده در لیست به دلیل مدیریت دستی حافظه ایجاد شده‌اند.
  • موارد ذکر شده به عنوان چالش‌ها و ویچیدگی‌های ++C/C معرفی شده‌اند نه معایب. چون  برای برنامه‌نویس‌های با تجربه کنار آمدن با این چالش‌ها کار چندان سختی نیست، و برای هر کدام راه حل مناسبی وجود دارد. 
  • موارد متعددی از چالش‌های این لیست محدود به زبان‌های C و ++C نمی‌شوند، و ممکن است در دیگر زبان‌های برنامه نویسی نیز وجود داشته باشند.
  • ممکن است مواردی باشد که در این لیست از قلم افتاده باشند. اگر موردی غیر از موارد بالا سراغ دارید، خوشحال می‌شویم که به ما اطلاع دهید.

جمع‌بندی

هرچند C و ++C ابزارهایی قدرتمند برای توسعه نرم‌افزارهای سریع و حساس هستند، اما در عین حال نیازمند دانش فنی بالا و دقت زیاد در طراحی و پیاده‌سازی دارند. استفاده از آن برای پروژه‌های پیچیده یا سطح پایین مناسب است، ولی برای پروژه‌های ساده و سریع بهتر است از زبان‌های سطح بالا و امن‌تر استفاده شود.در نهایت اینکه اگر قصد یادگیری ++C را دارید انتظار برخورد با یک زبان دشوار و گیج کننده را داشته باشید.

اولین نظر را ثبت کنید.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *