لماذا أحصل على خطأ تجزئة عند الكتابة إلى سلسلة تهيئة مع "char *s" ولكن ليس "حرف s[]"?

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

  •  03-07-2019
  •  | 
  •  

سؤال

التعليمة البرمجية التالية يتلقى seg خطأ على خط 2:

char *str = "string";
str[0] = 'z';  // could be also written as *str = 'z'
printf("%s\n", str);

في حين أن هذا يعمل بشكل جيد تماما:

char str[] = "string";
str[0] = 'z';
printf("%s\n", str);

اختبار مع MSVC ودول مجلس التعاون الخليجي.

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

المحلول

راجع ج أسئلة وأجوبة ، السؤال 1.32

س:ما هو الفرق بين هذه التهيئة?
char a[] = "string literal";
char *p = "string literal";
البرنامج تعطل إذا حاولت تعيين قيمة جديدة إلى p[i].

A:سلسلة حرفية (الرسمي الأجل على سلسلة مزدوجة-نقلا عن في ج المصدر) يمكن استخدامها في اثنين قليلا طرق مختلفة:

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

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

نصائح أخرى

عادة سلسلة حرفية المخزنة في ذاكرة القراءة فقط عندما يتم تشغيل البرنامج.هذا هو الحيلولة دون قصد منك تغيير سلسلة مستمرة.في المثال الأول ، "string" المخزنة في ذاكرة القراءة فقط ، *str يشير إلى الحرف الأول.على segfault يحدث عند محاولة تغيير الحرف الأول إلى 'z'.

في المثال الثاني ، سلسلة "string" هو نسخ بواسطة مترجم من القراءة فقط موطن str[] الصفيف.ثم تغيير الحرف الأول هو مسموح به.يمكنك التحقق من ذلك عن طريق طباعة عنوان كل:

printf("%p", str);

أيضا, الطباعة حجم str في المثال الثاني سوف تظهر لك أن المترجم قد خصصت 7 بايت ذلك:

printf("%d", sizeof(str));

معظم هذه الإجابات صحيحة ، ولكن فقط إلى إضافة المزيد من الوضوح...

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

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

لماذا أحصل على خطأ تجزئة عند الكتابة إلى سلسلة ؟

C99 N1256 مشروع

هناك نوعان من الاستخدامات المختلفة من الحرف سلسلة حرفية:

  1. تهيئة char[]:

    char c[] = "abc";      
    

    هذا هو "السحر" ، ووصف في 6.7.8/14 "التهيئة":

    مجموعة من نوع الحرف قد يكون تهيئة عن سلسلة أحرف الحرفي ، اختياريا المغلقة في الأقواس.الأحرف المتتالية الحرف سلسلة حرفية (بما في ذلك إنهاء null الحرف إذا كان هناك غرفة أو إذا كانت مجموعة غير معروفة الحجم) تهيئة عناصر المصفوفة.

    لذلك هذا هو مجرد اختصار:

    char c[] = {'a', 'b', 'c', '\0'};
    

    مثل أي أخرى العادية مجموعة ، c يمكن تعديلها.

  2. في كل مكان آخر:فإنه يولد:

    لذلك عندما تكتب:

    char *c = "abc";
    

    هذا هو مماثل:

    /* __unnamed is magic because modifying it gives UB. */
    static char __unnamed[] = "abc";
    char *c = __unnamed;
    

    ملاحظة ضمني من الزهر char[] إلى char *, الذي هو دائما القانونية.

    ثم إذا قمت بتعديل c[0], ، يمكنك أيضا تعديل __unnamed, الذي هو UB.

    هذا هو موثق في 6.4.5 "سلسلة حرفية":

    5 في مرحلة الترجمة 7 ، البايت أو رمز من القيمة صفر يتم إلحاق كل multibyte تسلسل الأحرف من سلسلة حرفية أو حرفية.على أحرف متعددة البايت تسلسل يستخدم بعد ذلك إلى تهيئة مجموعة من ساكنة التخزين ومدة طول يكفي أن تحتوي على التسلسل.حرف ل سلسلة حرفية ، مجموعة عناصر نوع char و هي تهيئة الفرد بايت من أحرف متعددة البايت سلسلة [...]

    6 هو غير محدد إذا كانت هذه المصفوفات هي متميزة وقدمت العناصر لديك القيم المناسبة.إذا كان البرنامج محاولات تعديل هذه مجموعة السلوك غير معرف.

6.7.8/32 "التهيئة" يعطي سبيل المثال المباشر:

مثال 8:الإعلان

char s[] = "abc", t[3] = "abc";

ويعرف "عادي" شار مجموعة الكائنات s و t عناصرها هي تهيئة مع الطابع سلسلة حرفية.

هذا الإعلان هو مطابق

char s[] = { 'a', 'b', 'c', '\0' },
t[] = { 'a', 'b', 'c' };

محتويات المصفوفات هي قابلة للتعديل.من ناحية أخرى, إعلان

char *p = "abc";

يعرف p مع نوع "مؤشر إلى شار" و تهيئة ذلك للإشارة إلى كائن مع نوع "مجموعة من شار" مع طول 4 عناصرها يتم تهيئة مع حرف سلسلة حرفية.إذا تم إجراء محاولة إلى استخدام p إلى تعديل محتويات الصفيف ، سلوك غير معرف.

دول مجلس التعاون الخليجي 4.8 x86-64 قزم تنفيذ

البرنامج:

#include <stdio.h>

int main(void) {
    char *s = "abc";
    printf("%s\n", s);
    return 0;
}

وتجميع وتفكيك:

gcc -ggdb -std=c99 -c main.c
objdump -Sr main.o

الناتج يحتوي على:

 char *s = "abc";
8:  48 c7 45 f8 00 00 00    movq   $0x0,-0x8(%rbp)
f:  00 
        c: R_X86_64_32S .rodata

الخلاصة:دول مجلس التعاون الخليجي مخازن char* في .rodata القسم وليس في .text.

إذا لم نفعل الشيء نفسه بالنسبة char[]:

 char s[] = "abc";

نحصل على:

17:   c7 45 f0 61 62 63 00    movl   $0x636261,-0x10(%rbp)

حتى يحصل تخزينها في المكدس (نسبة إلى %rbp).

ومع ذلك لاحظ أن الافتراضي رابط نصي يضع .rodata و .text في نفس القطاع ، والتي قد نفذ ولكن لا إذن الكتابة.هذا يمكن أن يكون لاحظت مع:

readelf -l a.out

الذي يحتوي على:

 Section to Segment mapping:
  Segment Sections...
   02     .text .rodata

في أول رمز "السلسلة" هو سلسلة مستمرة ، سلسلة ثوابت يجب أن لا يمكن تعديلها لأنها غالبا ما يتم وضعها في ذاكرة القراءة فقط."str" هو مؤشر يستخدم لتعديل مستمر.

في الثانية رمز "السلسلة" هو مجموعة مهيئ نوع من اليد قصيرة

char str[7] =  { 's', 't', 'r', 'i', 'n', 'g', '\0' };

"str" هو مجموعة المخصصة على المكدس و يمكن تعديلها بحرية.

لأنه نوع من "whatever" في سياق 1st المثال const char * (حتى إذا قمت بتعيين أنه إلى غير const char*) ، مما يعني أنك يجب أن لا محاولة الكتابة إليه.

مترجم فرض هذا عن طريق وضع سلسلة في القراءة فقط جزء من الذاكرة ، ومن ثم كتابة الأمر يولد segfault.

لفهم هذا الخطأ أو المشكلة يجب أن نعرف أولا الفرق ب/ث المؤشر و مجموعة حتى هنا أولا أنا أشرح لك الاختلافات b/w لهم

صفيف سلسلة

 char strarray[] = "hello";

في الذاكرة مجموعة يتم تخزينها في الذاكرة المستمر خلايا تخزين [h][e][l][l][o][\0] =>[] 1 شار بايت حجم الذاكرة المحمولة ,و هذا مستمر خلايا الذاكرة يمكن الوصول بالاسم اسمه strarray هنا.حتى هنا صفيف سلسلة strarray نفسها تحتوي على جميع الأحرف من سلسلة تهيئة إلى ذلك.في هذه الحالة هنا "hello" لذا نحن يمكن بسهولة تغيير محتوى الذاكرة عن طريق الوصول إلى كل حرف قبل المؤشر القيمة

`strarray[0]='m'` it access character at index 0 which is 'h'in strarray

وقيمته تغيير 'm' حتى strarray قيمة تغيير "mello";

نقطة واحدة أن نلاحظ هنا أن نتمكن من تغيير محتوى صفيف سلسلة طريق تغيير حرف ولكن لا يمكن تهيئة سلسلة أخرى مباشرة مثل strarray="new string" غير صالح

مؤشر

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

char *ptr = "hello";

هنا المؤشر ptr هو تهيئة السلسلة "hello" الذي هو ثابت سلسلة المخزنة في ذاكرة القراءة فقط (ROM) حتى "hello" لا يمكن تغيير كما هو المخزنة في ROM

و ptr يتم تخزينها في المكدس قسم لافتا إلى سلسلة ثابتة "hello"

حتى ptr[0]='m' غير صالح منذ كنت لا يمكن الوصول إلى ذاكرة القراءة فقط

ولكن ptr يمكن initialised الأخرى سلسلة القيمة مباشرة لأنه هو فقط مؤشر لذلك يمكن أن تشير إلى أي عنوان الذاكرة من متغير من نوع بيانات

ptr="new string"; is valid
char *str = "string";  

سبق مجموعات str للإشارة إلى القيمة الحرفية "string" وهو الثابت تلوينها في البرنامج الصورة الثنائية ، والتي ربما وضع علامة للقراءة فقط في الذاكرة.

لذلك str[0]= هو محاولة الكتابة إلى القراءة فقط رمز التطبيق.أعتقد أن هذا هو على الارجح مترجم تعتمد على الرغم من.

char *str = "string";

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

char str[] = "string";

تخصص تهيئة طائفة والمحلية التي للتعديل

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

على

 char *str = "string";

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

  str[0] = 'z';

يمكنك الحصول على seg خطأ.على بعض المنابر ، الحرفي قد يكون للكتابة في الذاكرة حتى أنك لن ترى segfault, لكنه غير صالح رمز (مما أدى إلى السلوك غير معرف) بغض النظر عن.

الخط:

char str[] = "string";

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

سلسلة حرفية مثل "السلسلة" هي على الأرجح تخصيص القابلة للتنفيذ الخاص بك عنوان مساحة للقراءة فقط البيانات (أو المترجم الخاص بك).عندما تذهب إلى تلمس ذلك النزوات أنك في السباحة ومنطقة يتيح لك معرفة مع seg خطأ.

في المثال الأول, كنت الحصول على مؤشر إلى أن const البيانات.في المثال الثاني ، أنت تهيئة مجموعة من 7 أحرف مع نسخة من const البيانات.

// create a string constant like this - will be read only
char *str_p;
str_p = "String constant";

// create an array of characters like this 
char *arr_p;
char arr[] = "String in an array";
arr_p = &arr[0];

// now we try to change a character in the array first, this will work
*arr_p = 'E';

// lets try to change the first character of the string contant
*str_p = 'G'; // this will result in a segmentation fault. Comment it out to work.


/*-----------------------------------------------------------------------------
 *  String constants can't be modified. A segmentation fault is the result,
 *  because most operating systems will not allow a write
 *  operation on read only memory.
 *-----------------------------------------------------------------------------*/

//print both strings to see if they have changed
printf("%s\n", str_p); //print the string without a variable
printf("%s\n", arr_p); //print the string, which is in an array. 

في المقام الأول ، str هو مؤشر يشير في "string".المترجم يسمح لوضع سلسلة حرفية في أماكن في الذاكرة التي لا يمكن كتابة ، ولكن يمكن قراءة فقط.(هذا حقا يجب أن أثار تحذيرا بما أنك تعيين const char * إلى char *.هل لديك تحذيرات تعطيل او تجاهلها؟)

في المركز الثاني ، إنشاء مجموعة التي هي الذاكرة التي لديك حق الوصول الكامل إلى تهيئة مع "string".أنت خلق char[7] (ستة على الحروف ، واحدة من أجل إنهاء '\0'), وأنت تفعل ما تريد مع ذلك.

نفترض السلاسل ،

char a[] = "string literal copied to stack";
char *p  = "string literal referenced by p";

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

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

الأول هو ثابت السلسلة التي لا يمكن تعديلها.الثاني هو مجموعة مع تهيئة قيمة ، لذلك فإنه يمكن تعديلها.

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

char *str هو مؤشر إلى سلسلة غير قابلة للتعديل(والسبب في الحصول على seg خطأ)..

في حين char str[] هو مجموعة و يمكن أن تكون قابلة للتعديل..

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