لماذا يتسبب رمز C العكسي للسلسلة في حدوث خطأ تجزئة؟[ينسخ]

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

  •  06-07-2019
  •  | 
  •  

سؤال

أحاول كتابة تعليمات برمجية لعكس سلسلة في مكانها (أحاول فقط أن أتحسن في برمجة لغة C ومعالجة المؤشرات)، لكن لا يمكنني معرفة سبب حصولي على خطأ تجزئة:

#include <string.h>

void reverse(char *s);

int main() {
    char* s = "teststring";
    reverse(s);

    return 0;
}

void reverse(char *s) {
    int i, j;
    char temp;

    for (i=0,j = (strlen(s)-1); i < j; i++, j--) {
        temp = *(s+i);     //line 1
        *(s+i) = *(s+j);   //line 2
        *(s+j) = temp;     //line 3
    }
}

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

تحديث:لقد قمت بتضمين وظيفة الاتصال كما هو مطلوب.

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

المحلول

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

كيف يمكنك استدعاء وظيفتك؟

تمت الإضافة:أنت تقوم بتمرير مؤشر إلى سلسلة حرفية.سلسلة حرفية غير قابلة للتعديل.لا يمكنك عكس سلسلة حرفية.

قم بتمرير مؤشر إلى سلسلة قابلة للتعديل بدلاً من ذلك

char s[] = "teststring";
reverse(s); 

وقد تم شرح هذا حتى الموت هنا بالفعل. "teststring" هي سلسلة حرفية.السلسلة الحرفية نفسها هي كائن غير قابل للتعديل.في الممارسة العملية، قد يضعها المترجمون (وسوف) في ذاكرة القراءة فقط.عند تهيئة مؤشر من هذا القبيل

char *s = "teststring";

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

const char *s = "teststring";

ولكن عندما تعلن الخاص بك s مثل

char s[] = "teststring";

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

في الأساس، الإعلان الأخير يعادل وظيفيا

char s[11];
strcpy(s, "teststring");

نصائح أخرى

يمكن أن يكون الكود الخاص بك مخطئًا لعدد من الأسباب.وهنا تلك التي تتبادر إلى الذهن

  1. s فارغة
  2. يشير s إلى سلسلة const التي يتم الاحتفاظ بها في ذاكرة القراءة فقط
  3. لم يتم إنهاء s NULL

أعتقد أن رقم 2 هو الأرجح.هل يمكنك أن تبين لنا موقع الاتصال العكسي؟

يحرر

بناءً على العينة رقم 2، فهي بالتأكيد الإجابة.السلسلة الحرفية في C/C++ غير قابلة للتعديل.النوع المناسب هو في الواقع const char* و لا char*.ما عليك القيام به هو تمرير سلسلة قابلة للتعديل إلى هذا المخزن المؤقت.

مثال سريع:

char* pStr = strdup("foobar");
reverse(pStr);
free(pStr);

هل تختبر هذا شيء من هذا القبيل؟

int main() {
    char * str = "foobar";
    reverse(str);
    printf("%s\n", str);
}

هذا يجعل str سلسلة حرفية وربما لن تتمكن من تعديلها (segfaults بالنسبة لي).إذا قمت بتحديد char * str = strdup(foobar) يجب أن تعمل بشكل جيد (يفعل بالنسبة لي).

تصريحك خاطئ تماما:

char* s = "teststring";

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

ولكن مع:

char s[] = "teststring";

تتم تهيئة s باستخدام "teststring"، والتي توجد بالطبع في مقطع التعليمات البرمجية، ولكن هناك عملية نسخ إضافية جارية إلى المكدس في هذه الحالة.

ما هو المترجم ومصحح الأخطاء الذي تستخدمه؟باستخدام gcc وgdb، سأقوم بتجميع الكود باستخدام علامة -g ثم تشغيله في gdb.عندما يتم تقسيمها، أود فقط إجراء تتبع خلفي (أمر bt في gdb) ومعرفة الخط المخالف الذي يسبب المشكلة.بالإضافة إلى ذلك، سأقوم فقط بتشغيل الكود خطوة بخطوة، بينما "أراقب" قيم المؤشر في gdb وأعرف أين تكمن المشكلة بالضبط.

حظ سعيد.

كما هو الحال في بعض الإجابات المذكورة أعلاه، فإن ذاكرة السلسلة للقراءة فقط.ومع ذلك، توفر بعض المترجمات خيارًا للتجميع باستخدام سلاسل قابلة للكتابة.على سبيل المثالمع gcc, ، إصدارات 3.x مدعومة -fwritable-strings لكن الإصدارات الأحدث لا تفعل ذلك.

يرى السؤال 1.32 في قائمة الأسئلة الشائعة لـ C:

ما هو الفرق بين هذه التهيئة؟

char a[] = "string literal";
char *p  = "string literal";

يتعطل برنامجي إذا حاولت تعيين قيمة جديدة له p[i].

إجابة:

يمكن استخدام السلسلة الحرفية (المصطلح الرسمي للسلسلة ذات علامات الاقتباس المزدوجة في مصدر C) بطريقتين مختلفتين قليلاً:

كمُهيئ لمجموعة من الأحرف، كما في إعلان char a[], ، فهو يحدد القيم الأولية للأحرف في هذا المصفوفة (وحجمها إذا لزم الأمر).

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

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

(التركيز الألغام)

أنظر أيضا الرجوع إلى الأساسيات بواسطة جويل.

أظن strlen لا يمكن أن تعمل لأن s لم يتم إنهاؤها بـ NULL.لذا فإن سلوك التكرار الخاص بك ليس هو السلوك الذي تتوقعه.نظرًا لأن نتيجة strlen ستكون أعلى من الطول، فسوف تكتب في الذاكرة حيث لا ينبغي أن تكون.

بالإضافة إلى ذلك، يشير s إلى سلاسل ثابتة يتم الاحتفاظ بها بواسطة ذاكرة القراءة فقط.لا يمكنك تعديله.حاول بدء s باستخدام الدالة gets كما هو الحال في سترلين مثال

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