Appearance
نحوه استفاده حرفه ای از احمقانه ترین چیز در کامپایلر ها
داستان کشف اهمیت دیباگ
معمولا وقتی داریم یک کد رو با g++ و یا gcc و .... کامپایل میکنیم ، بخش کثیری از نگرانیمون بخاطر ارور هایی هست که ممکنه بهمون داده بشه ! در حالت عادی ما هشداری از طرف کامپایلر دریافت نمی کنم ، من خودم قبلا در ادیتور ، گاها هشدار هایی دریافت میکردم. می تونم بگم که تا چند وقت پیش فکر می کردم هشدار های کامپایلر یکی از احمقانه ترین چیز های ممکن هستن!!
تا اینکه این ترم در طراحی الگوریتم، مجبور شدم با cpp کد بزنم، البته که می تونستم با پایتون و یا جاوا کد ها رو بزنم!! ولی حس میکردم به عنوان کسی که با جاوا و پایتون و c کار کرده ، باید یک نگاهی هم به cpp اندازه ، برای کسی که تازه برنامه نویسی رو شروع کرده جا به جا شدن بین زبان های مختلف برای مسیرش خیلی خطرناک هست ! برای کسی که داره کم کم به سمت مطالب پیشرفته میره بنظرم لازم هست که با چند زبان آشنایی کافی داشته باشه و با یک زبان بتونه به خوبی کار کنه.
از اونجایی که با سینتکس cpp آشنا بودم، یادگیری اولش چندان سخت نبود، شروع کردم به حل سوالات dp که داخل کتاب نیپولیتان مطرح بود. کمی که جلو رفتم و با سوالات سخت تری رو به رو شدم ، متوجه شدم که دو راه بیشتر ندارم، یا اینکه باید debug کنم و یا اینکه از LLM کمک بگیرم !!
هنوز هم مطمئن نیستم که این همه تایمی که برای دیباگ و زدن کد گذاشتم ارزشی داره یا نه؟!! من کمی وایب کدینگ هم کار میکنم ، ادیتور جاوا اسکریپت این وبلاگ رو داخل ایجنت gapcode ساختم و معماری بارگزاری یک فایل پیشفرض رو به کمک کوئری خودم در این ادیتور قرار دادم ! اکنون سال 2026 هست ، مدل هایی مثل gpt 5.5 ، کلاد آپس 4.8 و gemini 3.5 flash پرچمدار هستن و حقاّ که چه چیزهایی که نمیشه با این ها و به کمک ایجنت ساخت و فکر میکنم دیباگ چند کد ساده قرار هست به شدت بی ارزش بشه به عنوان کسی که زیاد علاقه داره ایده هاش رو با LLM پیاده سازی کنه !
بگذریم ، من بازه ای به برنامه نویس های دهه 90 فکر می کردم که چجوری بدون stackOverFlow و LLM کد هاشون رو دیپلوی میکردن و یک بازی و یا یک ابزار رو میساختن ! وقتی که شروع به دیباگ با gdb کردم، متوجه شدم که چه ابزار قدرتمندی برای برنامه نویس هاست! شاید بعدا به چند تکنیک که خودم برای دیباگ کردن توی gdb یاد گرفتم بپردازم.
تلههای زمان اجرا (Runtime)؛ بدترین چیز بعد از ایدز
همونطور که گفتم هشدار ها و یا وارنینگ ها در ظاهر به شدت احمقانه و یا دم دستی میان. چرا باید خودم رو درگیر هشدار کامپایلر درباره یک مقداری که ازش استفاده نشده بکنم ! عملا هیچ تاثیری توی کدم نداره !
مسئله این هست که گاهی میشه بدون استفاده از دیباگر خیلی از مشکلات کد رو حل کرد ! بعضی باگ ها در زمان کامپایل مشخص میشن و شاید یکی از راحت ترین باگ ها و یا ایرادات کد برای رفع کردن باشن، ایراد های دیگه خیلی وحشتناک تر ظاهر میشن !! داخل RUNTIME (زمان اجرا).
برای رفع اینجور ایراد ها باید ذهن باز و با تجربه ای در برخورد با این مشکلات داشته باشیم، گاها به شدت آدم رو غافل گیر میکنن! ممکن است خطایی که سبب شده یک برنامه چند هزار خطی از کار بیافتد استفاده از یک = به جای دو تا == هست (شرطی).
خب بعضی از این ها رو میشه به راحتی با وارنینگ ها دید ، مثلا مشکلی که در این کد وجود داره !
cpp
#include <iostream>
int main() {
int x = 0;
if (x = 5) {
std::cout << "Hello\n";
}
}این کد همیشه Hello رو چاپ میکنه ! چون که به جایی اینکه بررسی کنه که x با 5 برابر هست یا نه ، اون رو آساین میکنه و مقدار دهی میکنه و بلاک داخل if اجرا میشه. و لازم نیست توضیح بدم که اگر یک شرط همیشه درست باشه چه اتفاقی ممکنه بیفته!
دو راهکار برای یک اشتباه بزرگ: روش خلاقانه در مقابل روش حرفهای
برای حل این مشکل دو راه کار وجود داره. یکی خلاقانه و دیگری عاقلانه و حرفه ای.
۱. روش خلاقانه (Yoda Conditions)
در روش خلاقانه ما میایم شرط رو به این صورت مینویسم :
cpp
// If you accidentally type (5 = x), it will trigger a compiler error
if (5 == x) {
// code
}در این حالت اگر یک = رو کمتر بزاریم ، کامپایل ارور دریافت میکنیم در نتیجه متوجه این مشکل میشیم (بهتر از این هست که هیچ اروری دریافت نکنیم !!)
۲. روش عاقلانه (فلگ -Wall)
روش عاقلانه که قرار هست بیشتر دربارش صحبت کنیم استفاده از فلگ -Wall هست. (دقت کنید W بزرگ هست)
bash
g++ -Wall main.cpp -o mainو اینجاست که وارنینگ به درد ما میخوره.
خب روش های خلاقانه ام فعلا تا همین یکی بدرد میخوره 😦) ولی استفاده از وارنینگ ها کاربرد های خیلی بیشتری داره !
خطای تبدیل ولی نه برای برنامه نویسان مبتدی !!
به کد زیر دقت کنید:
cpp
int square(float x) {
return x * x;
}واضحه که مشکل کد بالا چیه (خروجی تابع int هست در حالی که ورودی و محاسبات ما float هست و اعشار عدد از بین میره). حتما هم نباید یک کد شبیه بالا باشه و برنامه نویس مورد نظر هم یک مبتدی یا یک آدم حواس پرت باشه. همین کد در مقیاس بزرگ تر و با خروجی هایی از نوع دیگه ممکن هست رخ بده و خطرناکترین بخشش این هست که کامپایلر در حالت عادی هیچ اروری نمیده !
در اینجا اگر کسی فلگ زیر رو فعال کرده باشه با یک وارنینگ رو به رو میشه:
-Wconversion
در ضمن -Wall برای بعضی از وارنینگ های مهم هست که ممکنه رخ بده و شامل همشون نمیشه ! و برعکس یکی قبلی، روش خلاقانه ای برای حل این مشکل وجود نداره یا حداقل خلاقیت من اونقدر نیست.
متغیرهای پنهانشده در سایه
فلگ دیگه ای که می خوام دربارش صحبت کنم فلگ زیر هست:
-Wshadow
به این کد دقت کنید:
cpp
#include <iostream>
int main() {
int x = 10;
{
int x = 20;
std::cout << x << std::endl;
}
}خب منطقا کد بالا هیچ ایرادی از نظر کامپایلر نداره! به عنوان یک انسان وقتی به این کد نگاه میکنید احتمالا میشه حدس زد که مقدار x سایه (Shadow) شده و یا به زبان دیگر یک مقدار بیرونی توسط مقدار درونی تغییر کرده که ممکنه عمدی باشه و یا از روی اشتباه باشه ولی خب یک warning میطلبه!!
با استفاده از فلگی که بالا به شما گفتم ، شما میتونید متوجه این موضوع بشید و اگر نیاز بود تغییرش بدید. خوب دقت کنید که مثال بالا به شدت مثال ساده ای هست، شاید کسی که با سی پلاس پلاس کار نکرده باشه و یا حتی کلا برنامه نویسی کار نکرده باشه متوجه یک ایرادی در این بجود بیاد!
کاربرد اصلی این در پروژه هایی هست که چند هزار خط کد و چند صد دپندنسی دارن که اون وسط ممکن هست به علت نامگذاری بد، همچین مشکلی در برنامه به وجود بیاد و البته که هیچ اروری هم توسط کامپایلر دریافت نمیکنه.
WARNING
بازم دقت کنید که W بزرگ هست.
تبدیل وارنینگ به خطا 😐
خب از اسم سرفصل برمیاد که زیاد ایده جذابی نباشه در نگاه اول یعنی تبدیل چیزهایی که از نظر کامپایلر یک ذره زشت و اشتباه هستن به چیزی که کامپایلر از صمیم قلب می خواد جلوش رو بگیره ! خب بزارین اول فلگش رو به شما بگم بعد بریم سراغ کاربرد اصلیش:
-Werror
یک فرهنگی در شرکت های بزرگ هست به این صورت که کد باید کاملا تمیز و بدون وارنینگ باشه.اگر بخوایم یک کد چند ده هزار خطی رو بدون وارنینگ نگه داریم ، قطعا چالش های زیادی داریم ولی مسئله دقیقا بخاطر این حجم از کد هست ! دقت کنید که اگر برفرض یک وارنینگ به معنای واقعی کلمه کم اهمیت باشه و اصلا کوچک ترین تاثیری هم نداشته باشه، در نهایت در یک پروژه با حجم زیاد ، این وارنینگ ها به چند صد تا وارنینگ میرسن و ممکنه در این میان یک وارنینگ که اهمیت زیادی داره گم بشه(هرچند میشه این رو مدیریت کرد و وارنینگ های خطرناک تر رو تشخیص داد) ولی تمیز نگاه داشتن کد و جلوگیری از وارنینگ انتخاب بهتری هست.
البته که دلیل بالا یکم من درآوردی بود ولی یک دلیل منطقی تر و فنی تر هم وجود داره.به این صورت که در شرکت های بزرگ از تست خودکار استفاده میکنن(CI/CD)، و این سرور ها این کد رو کامپایل و تست میکنن.پس این تست خودکار با دیدن وارنینگ جلوی دیپلوی رو میگیرن و تا وقتی که وارنینگ ها رفع نشن کد رو قرار نمیدن.(یک جورهایی در مرام و و ماهیت تست خودکار هست، چه دلیلی داره که وارنینگ ها رو اجازه بدیم وارد نرم افزار بشن در صورتی که تست خودکارمون به خوبی میتونه هندل کنه و جلوشون رو بگیره و توضیحاتی دربارشون ارائه بده؟!)
به صورت کلی یک دلیل اصلی و کلی بخوایم بگیم ، این هست که حتی بی اهمیت ترین وارنینگ نشان دهنده بی توجهی برنامه نویس و توسعه دهنده هست ! و هر کدوم ریسک نابود کردن یک سیستم کامل رو دارن !! پس میشه از توسعه دهنده انتظار داشت تا این مورد رو فیکس کنه.