الخطأخطأ التجزئة (الأساسية ملقاة) في C أو C++ يعني أن برنامجك حاول الوصول إلى الذاكرة وهو غير مسموح له بذلك. إنه أحد أكثر أخطاء C/C++ شيوعًا وإحباطًا. فيما يلي كيفية تشخيص جميع الأسباب الشائعة وإصلاحها.
📋 Table of Contents
- ما هو Segfault في الواقع
- السبب 1: إلغاء الإشارة إلى مؤشر فارغ
- السبب 2: تجاوز سعة المخزن المؤقت (الوصول خارج الحدود)
- السبب 3: الاستخدام بعد الحرة (المؤشر المتدلي)
- السبب 4: تجاوز سعة المكدس (العودة اللانهائية)
- تصحيح الأخطاء باستخدام gdb
- العثور على أخطاء في الذاكرة باستخدام Valgrind
- استخدام AddressSanitizer (سريع ومدمج)
- الأسئلة المتداولة
- الخلاصة
ما هو Segfault في الواقع
يحدث خطأ التجزئة عندما يصل برنامجك إلى الذاكرة خارج ما يمنحه نظام التشغيل له – مثل إلغاء الإشارة إلى مؤشر غير صالح، أو الكتابة بعد حدود المصفوفة، أو استخدام الذاكرة المحررة. يقوم نظام التشغيل بقتل البرنامج لحماية النظام ويتخلص من حالته (“التفريغ الأساسي”).
السبب 1: إلغاء الإشارة إلى مؤشر فارغ
// 🐛 Accessing a NULL or uninitialized pointer
int *ptr = NULL;
*ptr = 42; // ❌ segfault — writing to address 0
int *uninitialized; // points to garbage
*uninitialized = 5; // ❌ segfault (undefined behavior)
// ✅ Always initialize and check pointers
int value = 42;
int *ptr = &value
if (ptr != NULL) {
*ptr = 10; // safe
}
السبب 2: تجاوز سعة المخزن المؤقت (الوصول خارج الحدود)
// 🐛 Writing past the end of an array
int arr[5];
for (int i = 0; i <= 5; i++) { // ❌ i=5 is out of bounds
arr[i] = i; // arr[5] doesn't exist
}
// ✅ Correct bounds
for (int i = 0; i < 5; i++) { // i < 5, not <=
arr[i] = i;
}
// 🐛 strcpy overflow
char dest[5];
strcpy(dest, "This is too long"); // ❌ overflows dest
// ✅ Use bounded copy
char dest[20];
strncpy(dest, "safe copy", sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0';
السبب 3: الاستخدام بعد الحرة (المؤشر المتدلي)
// 🐛 Using memory after freeing it
int *ptr = malloc(sizeof(int));
*ptr = 42;
free(ptr);
*ptr = 10; // ❌ segfault — ptr is dangling
// ✅ Set to NULL after freeing
free(ptr);
ptr = NULL; // prevents accidental reuse
// if (ptr) *ptr = 10; // now safely skipped
السبب 4: تجاوز سعة المكدس (العودة اللانهائية)
// 🐛 Recursion with no base case exhausts the stack
int factorial(int n) {
return n * factorial(n - 1); // ❌ never stops → stack overflow
}
// ✅ Add a base case
int factorial(int n) {
if (n <= 1) return 1; // base case
return n * factorial(n - 1);
}
// 🐛 Huge local array can also overflow the stack
void func() {
int huge[10000000]; // ❌ too big for the stack
}
// ✅ Allocate large data on the heap
void func() {
int *huge = malloc(10000000 * sizeof(int));
// ... use it ...
free(huge);
}
تصحيح الأخطاء باستخدام gdb
# Compile with debug symbols
gcc -g -o myprogram myprogram.c
# Run under gdb
gdb ./myprogram
(gdb) run
# When it crashes:
Program received signal SIGSEGV, Segmentation fault.
0x0000... in main () at myprogram.c:15
# See the exact line and call stack
(gdb) backtrace # shows the call stack
(gdb) print ptr # inspect the bad pointer
(gdb) frame 1 # move up the stack
(gdb) list # show surrounding code
العثور على أخطاء في الذاكرة باستخدام Valgrind
# Valgrind catches invalid access, leaks, and use-after-free
valgrind --leak-check=full ./myprogram
# Output pinpoints the problem:
# Invalid write of size 4
# at 0x... in main (myprogram.c:15)
# Address 0x0 is not stack'd, malloc'd or (recently) free'd
# It shows exactly where and what kind of memory error occurred
استخدام AddressSanitizer (سريع ومدمج)
# Compile with AddressSanitizer — catches many bugs at runtime
gcc -fsanitize=address -g -o myprogram myprogram.c
./myprogram
# ASan reports:
# ERROR: AddressSanitizer: heap-buffer-overflow
# WRITE of size 4 at 0x...
# #0 in main myprogram.c:15
# Much faster than Valgrind and often clearer output
الأسئلة المتداولة
س: ماذا يعني “الأساسية الملقاة”؟
ج: عندما يتعطل البرنامج، يكتب نظام التشغيل “تفريغًا أساسيًا” – لقطة من ذاكرة البرنامج عند التعطل. يمكنك تحميله في gdb (gdb ./program core) لفحص الحالة لحظة التعطل.
س: لماذا يتعطل برنامجي أحيانًا ولكن ليس دائمًا؟
ج: السلوك غير المحدد (مثل استخدام ذاكرة غير مهيأة أو تجاوز طفيف للمخزن المؤقت) قد لا يؤدي دائمًا إلى حدوث عطل واضح – فهو يعتمد على ما يحدث في الذاكرة. وهذا يجعل مثل هذه الأخطاء خطيرة؛ استخدم Asan/Valgrind للقبض عليهم بشكل موثوق.
س: gdb vs Valgrind vs AddressSanitizer – ما الذي يجب أن أستخدمه؟
ج: يعتبر AddressSanitizer (علامة الترجمة) هو الأسرع ويكتشف معظم أخطاء الذاكرة – ابدأ هنا. Valgrind شامل للتسريبات ولا يحتاج إلى إعادة تجميع. gdb مخصص لفحص عطل معين بشكل تفاعلي. غالبًا ما تستخدم Asan أولاً، ثم gdb للحفر.
س: كيف يمكنني منع الأخطاء في المقام الأول؟
ج: قم بتهيئة المؤشرات، وتحقق من وجود NULL قبل إلغاء الإسناد، واحترم حدود المصفوفة، واضبط المؤشرات على NULL بعد التحرير، واستخدم مؤشرات C++ الذكية الحديثة (unique_ptr, shared_ptr) التي تدير الذاكرة تلقائيًا.
س: هل يجب علي استخدام المؤشرات الأولية في لغة C++ الحديثة؟
ج: تفضل المؤشرات والحاويات الذكية (المتجه، السلسلة) التي تتعامل مع الذاكرة بأمان وتزيل معظم أسباب الأخطاء. لا تزال المؤشرات الأولية مفيدة للمراجع التي لا تملكها، ولكن تجنب إجراء عملية جديدة/حذف يدويًا حيث تعمل أنواع RAII.
الخلاصة
خطأ التجزئة يعني وصولاً غير صالح للذاكرة — عادةً ما يكون مؤشرًا فارغًا/متدليًا، أو تجاوز سعة المخزن المؤقت، أو تجاوز سعة المكدس. سير عمل التصحيح:تجميع مع-fsanitize=address -gأو قم بتشغيله (يحدد AddressSanitizer الخط الدقيق ونوع الخطأ)، أو استخدم gdb’sbacktrace لمعرفة موقع العطل. لمنعها: قم بتهيئة المؤشرات، وحدد القيمة NULL، واحترم الحدود، والمؤشرات المحررة NULL-out، وفي C++ استخدم المؤشرات والحاويات الذكية. تعمل الأدوات الحديثة مثل Asan على تسهيل اكتشاف الأخطاء وإصلاحها، والتي كانت غامضة في السابق.
🔗 Share this article
✍️ Leave a Comment