Почему я получаю ошибку сегментации при записи в строку, инициализированную с помощью "char *s", но не"char s []"?


следующий код получает ошибку seg в строке 2:

char *str = "string";
str[0] = 'z';
printf("%sn", str);

хотя это работает отлично:

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

протестировано с MSVC и GCC.

16 238

16 ответов:

смотрите C FAQ,вопрос 1.32

Q: в чем разница между этими инициализациями?
char a[] = "string literal";
char *p = "string literal";
Моя программа аварийно завершает работу, если я пытаюсь присвоить новое значение p[i].

A: строковый литерал (формальный термин для двойные кавычки в C источник) можно использовать в 2 немножко разные способы:

  1. как инициализатор для массива чар, как в декларации char a[], он определяет начальные значения символов в этом массиве (и, при необходимости его размер).
  2. в любом другом месте он превращается в безымянный статический массив символов, и этот безымянный массив может быть сохранен в памяти только для чтения, и что поэтому не обязательно быть модифицированный. В контексте выражения , массив сразу преобразуется в a указатель, как обычно (см. раздел 6), так второе объявление инициализирует p к укажите на первый безымянный массив элемент.

некоторые компиляторы имеют переключатель управление ли строковые литералы доступны для записи или нет (для компиляции старых код), и некоторые из них могут иметь варианты заставить строковые литералы быть формально обрабатывается как массивы const char (для лучше ловить ошибки).

обычно строковые литералы хранятся в памяти только для чтения при запуске программы. Это делается для предотвращения случайного изменения Строковой константы. В вашем первом примере, "string" хранится в памяти только для чтения и *str указывает на первый символ. Segfault происходит при попытке изменить первый символ на 'z'.

во втором примере, строки "string" и скопировал компилятором из его дома только для чтения в str[] массив. Затем допускается изменение первого символа. Вы можете проверить это, напечатав адрес каждого:

printf("%p", str);

кроме того, печать в размер str во втором примере будет показано, что компилятор выделил для него 7 байт:

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

большинство из этих ответов верны, но просто добавить немного больше ясности...

"только для чтения памяти", что люди ссылаются на текстовый сегмент в терминах ASM. Это то же самое место в памяти, где инструкции загружаются. Это только для чтения по очевидным причинам, таким как безопасность. Когда вы создаете символ*, инициализированный в строку, строковые данные компилируются в текстовый сегмент, и программа инициализирует указатель, чтобы указать в текстовый сегмент. Так что если вы попытаетесь измени его, Кабум. Обработка выхода онлайн / оффлайн.

при записи в виде массива компилятор помещает инициализированные строковые данные в сегмент данных, который является тем же местом, что и ваши глобальные переменные и такие живые. Эта память изменчива, так как в сегменте данных нет инструкций. На этот раз, когда компилятор инициализирует массив символов (который все еще является просто символом*), он указывает на сегмент данных, а не на текстовый сегмент, который вы можете безопасно изменить во время выполнения.

в первом коде "строка" является Строковой константой, и строковые константы никогда не должны быть изменены, потому что они часто помещаются в память только для чтения. "str" - указатель, используемый для изменения константы.

во втором коде "string" является инициализатором массива, своего рода короткой рукой для

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

" str " - это массив, выделенный в стеке и может быть изменен свободно.

почему я получаю ошибку сегментации при записи в строку?

С99 проект N1256

есть два совершенно разных использования литералов массива:

  1. инициализации char[]:

    char c[] = "abc";      
    

    это "больше магии", и описанного в 6.7.8/14 "инициализация":

    массив символьного типа может быть инициализирован строку символов буквальное, при необходимости заключен в фигурные скобки. Последовательные символы символьного строкового литерала (включая завершающий нулевой символ, если есть номер или если массив неизвестного размера) инициализировать элементы массива.

    так что это просто ярлык для:

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

    как и любой другой обычный массив, 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, байт или код нулевое значение добавляется к каждому многобайтовых последовательность символов, которая является результатом строкового литерала или литералов. Многобайтовый символ последовательность затем используется для инициализации массива статической длительности хранения и длины просто достаточно, чтобы содержать последовательность. Для символьных строковых литералов, элементы массива иметь введите char, и инициализируются с отдельными байтами многобайтового символа последовательность.[ ..]

    6 Не указано, являются ли эти массивы различными при условии, что их элементы имеют нужных значений. Если программа попытается изменить такой массив, поведение будет не определено.

6.7.8/32 "инициализация" дает прямой пример:

пример 8: декларация

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

определяет "простые" объекты массива символов s и t элементы которого инициализируются символьными строковыми литералами.

эта декларация идентична

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

содержимое массивов можно изменять. С другой стороны, декларация

char *p = "abc";

определяет p С типом "указатель на char" и инициализирует его, чтобы указать на объект с типом "массив char" с длиной 4 элементы которого инициализируются символьным строковым литералом. Если сделана попытка использовать p чтобы изменить содержимое массива, поведение не определено.

GCC 4.8 x86-64 реализация Linux

давайте посмотрим, почему эта реализация segfaults.

программа:

#include <stdio.h>

int main() {
    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   x0,-0x8(%rbp)
f:  00 
        c: R_X86_64_32S .rodata

таким образом, строка хранится в элемент .

затем:

readelf -l a.out

содержит (упрощенный):

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x0000000000000704 0x0000000000000704  R E    200000

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

это означает, что сценарий компоновщика по умолчанию сбрасывает оба .text и .rodata в сегмент, который может быть выполнен, но не был изменен (Flags = R E). Попытка изменить такой сегмент приводит к segfault в Linux.

если мы сделаем то же самое для char[]:

 char s[] = "abc";

получаем:

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

таким образом, он сохраняется в стеке (относительно %rbp), и мы, конечно, можем изменить его.

потому что типа "whatever" в контексте 1-го примера это const char * (даже если вы назначаете его неконстантному символу*), что означает, что вы не должны пытаться писать в него.

компилятор применил это, поместив строку в часть памяти только для чтения, поэтому запись в нее создает segfault.

чтобы понять эту ошибку или проблему, вы должны сначала узнать разницу b / w указатель и массив так вот во-первых я должен объяснить вам различия ч / б их

строковый массив

 char strarray[] = "hello";

в памяти массив хранится в непрерывных ячейках памяти, хранящихся как [h][e][l][l][o][] =>[] является ячейкой памяти размером 1 char байт ,и эти непрерывные ячейки памяти могут быть доступны по имени strarray here.so вот строковый массив strarray содержит все символы строки, инициализированные в it.in этот случай здесь "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" не может быть изменен, так как он хранится в ПЗУ

и ptr хранится в разделе стека и указывает на постоянную строку "hello"

so ptr[0]=' m ' является недопустимым, так как вы не можете получить доступ только для чтения памяти

но ptr может быть инициализирован в другое строковое значение напрямую, так как это просто указатель, поэтому он может указывать на любой адрес памяти переменной своего типа данных

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

выше наборы str чтобы указать на литеральное значение "string" который жестко закодирован в двоичном образе программы, который, вероятно, помечен как только для чтения в памяти.

так str[0]= пытается записать в код приложения только для чтения. Я бы предположил,что это, вероятно, зависит от компилятора.

char *str = "string";

выделяет указатель на строковый литерал, который компилятор помещает в немодифицируемую часть вашего исполняемого файла;

char str[] = "string";

выделяет и инициализирует локальный массив, который можно изменить

C FAQ ,на который @ matli ссылается, упоминает об этом, но никто еще здесь не сделал этого, поэтому для уточнения: если строковый литерал (строка в двойных кавычках в вашем источнике) используется где угодно кроме чтобы инициализировать массив символов (т. е. второй пример @Mark, который работает правильно), эта строка хранится компилятором в специальном статическая таблица строк, что сродни созданию глобальной статической переменной (только для чтения, Конечно), которая по существу анонимна (не имеет имя переменной.)" Элемент только для чтения часть является важной частью, и именно поэтому первый пример кода @Mark segfaults.

The

 char *str = "string";

строка определяет указатель и указывает его в строковый литерал. Литеральная строка не доступна для записи, поэтому, когда вы делаете:

  str[0] = 'z';

вы получаете ошибку seg. На некоторых платформах литерал может находиться в доступной для записи памяти, поэтому вы не увидите segfault, но это недопустимый код (что приводит к неопределенному поведению) независимо.

строку:

char str[] = "string";

выделяет массив символов и копии строковый литерал в этот массив, который полностью доступен для записи, поэтому последующее обновление не является проблемой.

строковые литералы, такие как" 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'), и вы делаете с ним все, что хотите.

во-первых, это одна постоянная строка, которая не может быть изменена. Второй массив инициализируется значением, поэтому он может быть изменен.

ошибка сегментации возникает, когда вы tyr для доступа к памяти, которая не доступна.

char *str - это указатель на строку, которая не может быть изменена (причина получения ошибки seg)..

, тогда как char str[] массив и может быть изменяемым..