ما هو خطأ التجزئة (Segfault) وكيف تصلحه؟
خطأ التجزئة (segmentation fault أو segfault) تعطل يحدث عندما يحاول البرنامج قراءة ذاكرة أو الكتابة فيها دون أن يُسمح له بالوصول إليها، مثل العنوان 0 عبر مؤشر فارغ. يوقف نظام التشغيل البرنامج بالإشارة SIGSEGV.
آخر تحديث 24 سبتمبر 2026
برنامج بلغة C يطبع سطره الأول ثم يتوقف برسالة لم يكتبها أحد:
#include <stdio.h>
int main(void) {
int *score = NULL;
printf("About to read the score\n");
printf("Score: %d\n", *score);
return 0;
}
About to read the score
bash: line 1: 64030 Segmentation fault: 11 ./seg
هذه المخرجات من bash على macOS، حيث 64030 هو معرّف العملية و11 هو رقم الإشارة. أما على لينكس فالتعطل نفسه يظهر عادةً بصيغة Segmentation fault (core dumped). في الحالتين لم يطبع البرنامج الدرجة أبدًا. يحمل score العنوان 0، و*score يطلب من المعالج قراءة الذاكرة عند ذلك العنوان، وهذا ما لا يُسمح لأي برنامج بفعله.
كيف يحدث خطأ التجزئة
يعمل كل برنامج في فضاء عناوين افتراضي خاص به، وهو نطاق هائل من العناوين يملؤه نظام التشغيل قطعة قطعة. يربط فيه كود البرنامج ومتغيراته العامة ومكدسه (stack) وكومته (heap)، في كتل تسمى صفحات (4 KB في معظم أنظمة لينكس على x86، و16 KB في أجهزة Mac ذات معالجات Apple silicon). تبقى معظم العناوين غير مربوطة، والعنوان 0 من بينها دائمًا حتى تُكتشف أخطاء المؤشرات الفارغة.
- ينفّذ البرنامج تعليمة تقرأ من عنوان أو تكتب فيه. هنا هي قراءة
*score، أي العنوان 0. - تبحث وحدة إدارة الذاكرة في المعالج عن العنوان في جدول الصفحات. الصفحة غير مربوطة، أو أن البرنامج يحاول الكتابة في صفحة للقراءة فقط.
- يوقف المعالج التعليمة ويسلّم التحكم إلى النواة (kernel) بخطأ صفحة (page fault).
- تتحقق النواة مما إذا كان الوصول يمكن أن يكون مشروعًا، كمكدس يحتاج إلى أن يكبر مثلًا. ليس كذلك، فترسل النواة إلى العملية الإشارة 11، أي
SIGSEGV. - الإجراء الافتراضي لـ
SIGSEGVهو إنهاء العملية، وحفظ core dump إن سمح النظام بذلك. ثم تطبع الصدفة الرسالة وتضبط حالة الخروج على 139، وهي 128 مضافًا إليها رقم الإشارة.
إذن خطأ التجزئة لا يُبلغ عنه المترجم، وليس استثناءً ترفعه اللغة. إنه العتاد والنواة يحميان الذاكرة، مما يجعله خطأ وقت تشغيل من أكثر الأنواع فجائية. ويتعامل ويندوز مع الحدث نفسه على أنه access violation، برمز الاستثناء 0xC0000005.
أشهر أسباب خطأ التجزئة
تسمح C و C++ للبرنامج بحساب أي عنوان واستخدامه، فتعود الأسباب كلها إلى استخدام عنوان غير صالح.
- فك مرجع مؤشر فارغ.
int *p = NULL; *p = 5;الدالة التي تُرجعNULLعند الفشل، مثلmallocأوfopen، تؤدي إلى هذا حين لا تُفحص نتيجتها. - فهرس بعيد جدًا عن نهاية المصفوفة. لا تفحص C الحدود، فـ
arr[1000000]مجرد عنوان يقع بعد مليون عنصر. - استخدام الذاكرة بعد
free. لا يزال المؤشر يحمل العنوان القديم، لكن الذاكرة لم تعد ملكك. - مؤشر غير مُهيَّأ.
int *p; *p = 5;تكتب عبر أي قيمة عشوائية يصادف أن يحملهاp. - فيضان المكدس (stack overflow). دالة استدعاء ذاتي بلا حالة توقف تستمر في إضافة إطارات إلى المكدس حتى تتجاوز نهايته. المثال أدناه تعطل على macOS بـ
Segmentation fault: 11وحالة خروج 139. - الكتابة في نص حرفي (string literal).
char *name = "coddy"; name[0] = 'C';تحاول تغيير ذاكرة للقراءة فقط. على لينكس هذا خطأ تجزئة. وعلى macOS توقف البرنامج نفسه بـBus error: 10، وهي إشارة قريبة.
#include <stdio.h>
int depth(int n) {
return depth(n + 1) + 1; /* never stops calling itself */
}
int main(void) {
printf("%d\n", depth(0));
return 0;
}
الخطأ الصغير كثيرًا ما لا يسبب تعطلًا على الإطلاق، وهذا ما يجعله أخطر. هذه الحلقة تقرأ عنصرًا واحدًا بعد نهاية مصفوفة من ثلاثة عناصر:
#include <stdio.h>
int main(void) {
int scores[3] = {72, 88, 95};
int total = 0;
for (int i = 0; i <= 3; i++) { /* <= reads scores[3] */
total += scores[i];
}
printf("Total: %d\n", total);
return 0;
}
عند ترجمته باستخدام Clang على جهاز Mac طبع Total: 256. والمجموع الصحيح 255. كان scores[3] هو الـ 4 بايتات التالية في المكدس، وكانت ملكًا للبرنامج، فلم يقع أي عطل وأُضيفت القيمة العشوائية بصمت. نظام التشغيل لا يوقف إلا الوصول إلى ذاكرة لا يملكها البرنامج إطلاقًا.
إصلاح الخطأين هو العادة نفسها: اعرف كم عنصرًا لديك، وافحص المؤشر قبل أن تتبعه.
Best: 95
No scores, nothing to read
ماذا تعني "core dumped"
الـ core dump ملف يحمل نسخة من ذاكرة البرنامج لحظة تعطله. يمكن للمُنقِّح فتحه لاحقًا وإظهار المكان الذي كان فيه البرنامج بالضبط وما كانت تحمله متغيراته: gdb ./app core. في كثير من توزيعات لينكس يجمع systemd-coredump هذه الملفات، ويعرضها coredumpctl list. وعندما تكون الـ core dumps معطلة، مثلًا باستخدام ulimit -c 0، تكون الرسالة Segmentation fault فقط، بلا الكلمات التي بين القوسين.
كيف تجد السطر الذي تعطل
السطر الذي يتعطل فيه البرنامج كثيرًا ما لا يكون السطر الذي فيه الخلل. قد يصبح المؤشر غير صالح في دالة ثم يُستخدم في دالة أخرى بعد ذلك بكثير. هذه الأدوات تُظهر الاثنين.
المُنقِّح (debugger). ترجم مع معلومات التنقيح وشغّل البرنامج داخل المُنقِّح. عندما يتوقف، يطبع bt (backtrace) سلسلة استدعاءات الدوال مع أسماء الملفات وأرقام الأسطر. على لينكس يكون المُنقِّح عادةً gdb، وعلى macOS هو lldb، حيث يعمل bt بالطريقة نفسها.
gcc -g app.c -o app
gdb ./app
(gdb) run
(gdb) bt
AddressSanitizer. ترجم باستخدام -fsanitize=address (يدعمه كل من GCC و Clang) وشغّل البرنامج بشكل عادي. بدل خطأ تجزئة مجرد، يطبع تقريرًا يسمّي نوع الخطأ، مثل heap-use-after-free أو stack-buffer-overflow، مع السطر الذي قام بالوصول والسطر الذي حجز الذاكرة. كما يلتقط القراءة الصامتة بعد النهاية بعنصر واحد المذكورة أعلاه، التي لا تتعطل من تلقاء نفسها أبدًا.
Valgrind. على لينكس، يشغّل valgrind ./app برنامجًا غير معدّل ويُبلغ عن كل قراءة أو كتابة غير صالحة، مثل Invalid read of size 4.
خطأ التجزئة في لغات أخرى
تفحص بايثون وجافا وجافا سكريبت كل فهرس وكل مرجع قبل استخدامه، فتصبح الأخطاء نفسها استثناءات برسائل واضحة: IndexError أو AttributeError في بايثون، وArrayIndexOutOfBoundsException أو NullPointerException في جافا، وTypeError في جافا سكريبت. ويمكن التقاطها بـ معالجة الاستثناءات. أما خطأ التجزئة فلا يمكن معالجته بهذه الطريقة: إنه إشارة، وكتلة catch في C++ لا تراه.
لكن برامج بايثون قد تتعطل بخطأ التجزئة حين يفشل كود C تحتها. هذا السطر يطلب من ctypes قراءة العنوان 0:
import ctypes
ctypes.string_at(0)
عند تشغيله بـ python3 -X faulthandler طبعت بايثون 3.12 العبارة Fatal Python error: Segmentation fault، تليها أسطر بايثون التي كانت تُنفَّذ. ويحدث التعطل نفسه حين يكون في امتداد C أو مكتبة أصلية (native) خلل في الذاكرة. أما Rust فتسلك نهجًا مختلفًا: يرفض مترجمها معظم الكود الذي قد يصل إلى ذاكرة غير صالحة قبل بناء البرنامج.
إلى أين بعد ذلك
يشرح دليل C عن أخطاء التجزئة كل سبب ببرنامج صغير وإصلاحه. ولتتجنب هذه الأخطاء من البداية، اقرأ عن المؤشرات والمؤشرات الفارغة والمكدس والكومة، ثم تدرّب عليها في دورة C. ولمعرفة كيف يُبلَّغ عن الأخطاء في اللغات التي تفحص الذاكرة نيابة عنك، راجع صفحة خطأ وقت التشغيل.
الأسئلة الشائعة
كيف أصلح خطأ التجزئة؟
-g وشغّل البرنامج داخل gdb أو lldb، ثم اكتب bt بعد التعطل، أو ترجم باستخدام -fsanitize=address للحصول على تقرير مفصل. ثم أصلح المؤشر أو الفهرس الذي يستخدمه ذلك السطر: افحص المؤشرات مقابل NULL، وأبقِ فهارس المصفوفات أقل من طولها، وتوقف عن استخدام الذاكرة بعد free، وتأكد من أن الاستدعاء الذاتي ينتهي.هل خطأ التجزئة هو تسرب في الذاكرة؟
free، مثل استخدام مؤشر بعد تحريره، قد تسبب أخطاء التجزئة، بينما نسيان free يسبب التسرب.لماذا يسمى خطأ التجزئة بهذا الاسم؟
SIGSEGV. ويسمي ويندوز الحدث نفسه access violation.هل يمكن أن يحدث خطأ التجزئة في بايثون؟
ctypes. تشغيل python -X faulthandler script.py يطبع أسطر بايثون التي كانت تُنفَّذ لحظة التعطل.ماذا يعني رمز الخروج 139؟
SIGSEGV، أي خطأ التجزئة. تُبلغ الأصداف (shells) عن الموت بإشارة بالقيمة 128 مضافًا إليها رقم الإشارة، و128 + 11 = 139. وفي Docker و Kubernetes، الحاوية التي تخرج بالرمز 139 تعرضت عمليتها الرئيسية لخطأ التجزئة.