Почему я получаю ошибку сегментации при записи в строку, инициализированную с помощью "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 ответов:
смотрите C FAQ,вопрос 1.32
Q: в чем разница между этими инициализациями?
char a[] = "string literal";
char *p = "string literal";
Моя программа аварийно завершает работу, если я пытаюсь присвоить новое значениеp[i]
.A: строковый литерал (формальный термин для двойные кавычки в C источник) можно использовать в 2 немножко разные способы:
- как инициализатор для массива чар, как в декларации
char a[]
, он определяет начальные значения символов в этом массиве (и, при необходимости его размер).- в любом другом месте он превращается в безымянный статический массив символов, и этот безымянный массив может быть сохранен в памяти только для чтения, и что поэтому не обязательно быть модифицированный. В контексте выражения , массив сразу преобразуется в 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
есть два совершенно разных использования литералов массива:
инициализации
char[]
:char c[] = "abc";
это "больше магии", и описанного в 6.7.8/14 "инициализация":
массив символьного типа может быть инициализирован строку символов буквальное, при необходимости заключен в фигурные скобки. Последовательные символы символьного строкового литерала (включая завершающий нулевой символ, если есть номер или если массив неизвестного размера) инициализировать элементы массива.
так что это просто ярлык для:
char c[] = {'a', 'b', 'c', ''};
как и любой другой обычный массив,
c
может быть изменен.везде: это создает сообщение:
- безымянный
- массив char какой тип строковых литералов в C и C++?
- со статической памятью
- что дает UB при изменении
поэтому, когда вы пишете:
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'), и вы делаете с ним все, что хотите.