تصحيح أخطاء كود C++: كيف تتوقف عن الخوف من segmentation fault وتحب GDB
هل سبق لك أن أمضيت ثلاث ساعات في البحث عن خطأ تبين في النهاية أنه break منسي في switch أو ; زائد بعد if؟ إذا كان الأمر كذلك، مرحباً بك في النادي. تصحيح أخطاء C++ هو فن يلامس المازوخية. ولكن، مثل أي فن، يمكن إتقانه.
في هذه المقالة، سنناقش ليس فقط "كيفية تشغيل GDB"، بل تقنيات حقيقية تحول تصحيح الأخطاء من التخمين إلى عمل استقصائي على طريقة شيرلوك هولمز. سنتحدث عن أدوات التعقيم الحديثة، وتصحيح الأخطاء العكسي، وحتى كيفية اكتشاف خطأ يظهر "فقط يوم الجمعة بعد الظهر".
1. كلاسيكيات النوع: GDB و LLDB
لنبدأ بالأساسيات. GNU Debugger (GDB) ومنافسه LLDB هما سكاكين المصحح السويسرية. لكن معظم الناس يستخدمون 10% فقط من إمكانياتهما. دعنا نصلح هذا.
نقاط التوقف الشرطية
هل تضع نقطة توقف في حلقة من 10,000 تكرار وتضغط continue 9,999 مرة؟ توقف. استخدم نقاط التوقف الشرطية:
(gdb) break main.cpp:42 if i == 5000هذا سيوفر لك ساعات من حياتك. في LLDB، الصيغة مشابهة:
(lldb) breakpoint set --file main.cpp --line 42 --condition 'i == 5000'Watchpoints - اصطياد تغيير المتغير
تخيل: لديك متغير يتغير في مكان غير معروف. نضع watchpoint:
(gdb) watch my_variable(gdb) rwatch my_variable # سيعمل عند القراءة(gdb) awatch my_variable # سيعمل عند القراءة والكتابةالآن سيتوقف البرنامج تماماً في اللحظة التي يلمس فيها أحد متغيرك. سحر، أليس كذلك؟
2. Reverse Debugging: السفر عبر الزمن
أسوأ كابوس للمبرمج: البرنامج تعطل، لكنك لا تعرف كيف وصل إلى هذه الحالة. تصحيح الأخطاء العكسي (Reverse Debugging) يسمح لك "بإرجاع" التنفيذ إلى الوراء.
في GDB، تحتاج إلى دعم record:
(gdb) record(gdb) continue... البرنامج تعطل ...(gdb) reverse-step # خطوة للخلف(gdb) reverse-continue # استمرار في الاتجاه المعاكسفي الأدوات التجارية (مثل UndoDB أو rr من Mozilla) يعمل هذا بشكل أفضل. rr (Record and Replay) هو أداة مجانية تسجل تنفيذ البرنامج، ثم تسمح بتشغيله مراراً وتكراراً:
$ rr record ./my_program$ rr replay(gdb) continue... وجدنا مكان التعطل ...(gdb) reverse-stepنصيحة: استخدم rr للبحث عن سباقات البيانات (data races) - فهو يعيد إنتاج كل تنفيذ بشكل حتمي.
3. أدوات التعقيم: تطعيم ضد السلوك غير المحدد
C++ الحديث (بدءاً من Clang و GCC 4.8+) يحتوي على أدوات مدمجة لم يحلم بها آباؤنا. AddressSanitizer (ASan) و UndefinedBehaviorSanitizer (UBSan) و ThreadSanitizer (TSan) هي أدوات لا غنى عنها.
مثال: اصطياد تسرب الذاكرة
// compile with: g++ -fsanitize=address -g main.cpp -o main#include <iostream>
void leak() { int* p = new int(42); // نسينا delete}
int main() { leak(); return 0;}نقوم بالتشغيل - ونرى:
==12345==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 4 byte(s) in 1 object(s) allocated from: #0 0x7f... in operator new(unsigned long) #1 0x400... in leak() main.cpp:5 #2 0x400... in main main.cpp:10سيعرض ASan مكدس الاستدعاءات الدقيق، بما في ذلك رقم السطر الذي حدث فيه التخصيص. لا مزيد من "الذاكرة تتسرب في مكان ما".
UBSan ضد السلوك غير المحدد
// compile with: g++ -fsanitize=undefined -g main.cppint main() { int x = INT_M