لماذا يتسبب كود C++ الخاص بي في حدوث خطأ تجزئة بعد استخدام وظيفة القراءة (...)؟

StackOverflow https://stackoverflow.com/questions/677444

سؤال

يتم تعليق تطبيقي على سطر من التعليمات البرمجية يبدو أنه لا يوجد به أي خطأ، ولكن يبدو أن IDE الخاص بي معلق على هذا السطر مع وجود الخطأ:

gdb/mi (24/03/09 13:36) (خرج.تم استلام الإشارة "SIGSEGV".وصف:خطأ تجزئة.)

يستدعي سطر التعليمات البرمجية ببساطة طريقة لا تحتوي على تعليمات برمجية.ليس خطأ تجزئة عندما يكون لديك مرجع فارغ؟إذا كان الأمر كذلك، كيف يمكن أن يكون للطريقة الفارغة مرجع فارغ؟

يبدو أن هذا الجزء من التعليمات البرمجية هو سبب المشكلة:

#include <sys/socket.h>

#define BUFFER_SIZE 256

char *buffer;

buffer = (char*)GetSomePointer()->SomeStackMemoryString.c_str();
int writeResult = write(socketFD, buffer, BUFFER_SIZE);

bzero(buffer, BUFFER_SIZE);
int readResult = read(socketFD, buffer, BUFFER_SIZE);

عندما يكون الخط باستخدام read(...) تم التعليق على الطريقة، وتختفي المشكلة.

تحديث:

لقد قمت بتغيير السؤال للإشارة إلى المشكلة الفعلية، وقمت بإزالة كافة التعليمات البرمجية غير ذات الصلة - وقمت أيضًا أجاب على سؤالي الخاص لكي يعرف الأشخاص الذين يقرؤون هذا الأمر على وجه التحديد ما هي المشكلة، يرجى قراءة إجابتي قبل أن تقول "أنت معتوه!".

هل كانت مفيدة؟

المحلول

والتعليمات البرمجية زائف: نقاط عازلة لبعض قطعة عشوائي من الذاكرة. لست متأكدا من السبب في خط مع bzero لا تفشل.

ورمز الصحيح هو:

   char buffer[BUFFER_SIZE];

   bzero(buffer, BUFFER_SIZE);
   int readResult = read(socketFD, buffer, BUFFER_SIZE);

وأو يمكنك استخدام calloc (1، BUFFER_SIZE) للحصول على بعض الذاكرة المخصصة (وركزت الخروج).

نصائح أخرى

أولا، استدعاء أسلوب من خلال مؤشر فارغة أو إشارة يتحدث بدقة سلوك غير معرف. لكنها قد تنجح إلا إذا كانت المكالمة الظاهري.

ودعوة وسائل افتراضية تقريبا (من خلال مؤشر / إشارة، وليس من فئة مشتقة مع فئة :: طريقة () وسيلة من invokation) دائما يفشل إذا كانت الإشارة / مؤشر باطل لأن مكالمات افتراضية تتطلب الوصول إلى vtable والوصول إلى vtable من خلال مؤشر فارغة / إشارة مستحيلة. لذلك لا يمكنك استدعاء أسلوب ظاهري فارغة من خلال إشارة / المؤشر.

لفهم هذه تحتاج إلى معرفة المزيد عن كيفية تنظيم التعليمات البرمجية. لكل طريقة غير inlined هناك جزء من مقطع التعليمات البرمجية التي تحتوي على رمز الجهاز تنفيذ الأسلوب.

وعندما يتم دعوة غير تقريبا (إما من فئة مشتقة أو أسلوب غير ظاهري من خلال إشارة / المؤشر) المترجم يعرف بالضبط الطريقة التي يجب استدعاء (أي تعدد الأشكال). لذلك مجرد إدراج مكالمة إلى الجزء المحدد من التعليمات البرمجية ويمر <م> هذه مؤشر كمعلمة الأولى هناك. في حالة الاتصال من خلال مؤشر فارغة <م> هذه سيكون لاغيا أيضا، ولكن كنت لا تهتم إذا طريقتك فارغ.

وعندما يتم استدعاء تقريبا (من خلال إشارة / المؤشر) المترجم لا يعرف الطريقة التي بالضبط للاتصال، فهو يعرف فقط أن هناك جدول الطرق الافتراضية وعنوان الجدول يتم تخزينها في الجسم. من أجل إيجاد طريقة ما لدعوة من الضروري أولا dereference المؤشر / إشارة، والحصول على الطاولة، والحصول على عنوان طريقة منه وعندها فقط استدعاء الأسلوب. قراءة الجدول يتم في وقت التشغيل، وليس أثناء الترجمة. إذا كان مؤشر / مرجع فارغة تحصل خطأ تجزئة عند هذه النقطة.

وهذا ما يفسر أيضا لماذا مكالمات افتراضية لا يمكن inlined. المترجم له ببساطة أي فكرة عما الرمز إلى مضمنة عندما يبحث في المصدر أثناء الترجمة.

بدون الكود، أفضل ما يمكنني فعله هو التخمين الجامح.ولكن هنا يذهب:

"التعليمات البرمجية طويلة المدى" الخاصة بك تكتب إلى مؤشر غير صالح.(إما مؤشر عشوائي تمامًا، أو تجاوز بداية/بداية المخزن المؤقت أو المصفوفة).يحدث هذا يؤدي إلى التشويش على جدول الوظائف الافتراضية للكائن الخاص بك - إما أنه يقوم بالكتابة فوق المؤشر إلى الكائن، أو عضو vptr للكائن، أو أنه يقوم بالكتابة فوق جدول الوظائف الافتراضية العالمي الفعلي لتلك الفئة.

بعض الأشياء التي يمكنك تجربتها:

  • ضع عضوًا حارسًا في صفك.على سبيل المثالint الذي تمت تهيئته لنمط معروف (0xdeadbeef أو 0xcafebabe شائعان) في مُنشئك، ولم يتغير أبدًا.قبل إجراء استدعاء الوظيفة الافتراضية، تحقق من (assert()) أنها لا تزال تحتوي على القيمة الصحيحة.
  • حاول استخدام مصحح أخطاء الذاكرة.في Linux، تتضمن الخيارات Electric Fence (السياج) أو Valgrind.
  • قم بتشغيل برنامجك ضمن مصحح أخطاء (gdb جيد) وقم بالبحث لمعرفة ما هو الخطأ - إما بعد الوفاة بعد حدوث segfault، أو عن طريق تعيين نقطة توقف قبل المكان الذي سينتقل إليه segfault.

وأنا لا أستطيع التفكير في أي سبب وسيلة فارغة من تلقاء نفسها أن يسبب مثل هذه المشكلة. دون أي سياق آخر، فإن الرغم الأول لي أن يكون هذا مشكلة في أي مكان آخر يفسد الذاكرة الخاصة بك وذلك فقط حتى يحدث أن يعبر عن نفسه بهذه الطريقة هنا.

وكان لدينا هذا النوع من المشكلة من قبل، وكتبت عن ذلك في هذه الإجابة <لأ href = "https://stackoverflow.com/questions/7525/of-memory-management-heap-corruption-and-c / 71983 # 71983 "> هنا . هذا السؤال نفسه لديه الكثير من النصائح الجيدة الأخرى في ذلك أيضا والتي قد تساعدك.

<اقتباس فقرة>   

وليس خطأ تجزئة عندما يكون لديك مرجع فارغة؟

وربما، ولكن ليس بالضرورة. ما الذي يسبب segfault إلى حد ما نظام أساسي محددة، ولكنه يعني أساسا أن البرنامج يتم الوصول إلى الذاكرة التي لا ينبغي أن يكون. قد ترغب في قراءة ويكيبيديا المقالة للحصول على فكرة أفضل عن ما هو عليه.

وشيء واحد قد تحقق على، لا طريقة فارغة لديهم نوع المقابل؟ قد أكون مخطئا في هذا، ولكن إذا كان بإرجاع كائن، وكنت أرى كيف يمكن لمنشئ نسخة يمكن الحصول دعا القمامة إذا كانت الطريقة ليست في الواقع تعود كائن. وهذا يمكن أن يسبب كل أنواع السلوك متزعزع.

هل تحصل على نفس النتيجة إذا قمت بتغيير نوع عودتها إلى باطل أو يمكنك إرجاع قيمة؟

والمسألة هي لأن المتغير buffer هو استخدام الذاكرة غير المعينة، والذي يسبب تلف الذاكرة عندما يضع وظيفة read(...) البيانات في buffer.

وعادة، bzero من شأنه أن يسبب في الواقع خطأ تجزئة، ولكن لأنه يتم تعيين السلسلة إلى موقع الذاكرة، سمح وظيفة القراءة لكتابة الماضي الذاكرة المخصصة (مما تسبب في تسرب).

/* this causes *some* memory to be allocated, 
 * tricking bzero(...) to not SIGSEGV */
buffer = (char*)GetSomePointer()->SomeStackMemoryString.c_str();

int writeResult = write(socketFD, buffer, BUFFER_SIZE);

وهذا التغيير لا يحل تسرب للذاكرة:

#define BUFFER_SIZE 256

// Use memory on the stack, for auto allocation and release.
char buffer[BUFFER_SIZE];

// Don't write to the buffer, just pass in the chars on their own.
string writeString = GetSomePointer()->SomeStackMemoryString;
int writeResult = write(socketFD, writeString.c_str(), writeString.length());

// It's now safe to use the buffer, as stack memory is used.
bzero(buffer, BUFFER_SIZE);
int readResult = read(socketFD, buffer, BUFFER_SIZE);

هل استدعاء الأسلوب الظاهري من المنشئ من الفئة الأساسية؟ يمكن أن تكون المشكلة: إذا كنت استدعاء أسلوب الظاهري النقي من Base فئة في منشئ Base، ووفقط تعريفه في الواقع في Derived الدرجة، قد ينتهي بك الأمر الوصول إلى سجل vtable التي لم تحدد بعد، لأن منشئ Derived ل لم يتم تنفيذها في هذه النقطة.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top