Следующий код получает ошибку сегмента в строке 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 и GCC.
c
segmentation-fault
c-strings
Markus
источник
источник
Ответы:
См. C FAQ, Вопрос 1.32
источник
mprotect
защиту только для чтения (см. Здесь ).Обычно строковые литералы хранятся в постоянной памяти только при запуске программы. Это предотвращает случайное изменение строковой константы. В первом примере
"string"
он хранится в постоянной памяти и*str
указывает на первый символ. Segfault происходит, когда вы пытаетесь изменить первый символ на'z'
.Во втором примере строка
"string"
будет скопирована компилятором из его только для чтения дома вstr[]
массиве. Тогда изменение первого символа разрешено. Вы можете проверить это, напечатав адрес каждого:Кроме того, печать размера
str
во втором примере покажет вам, что компилятор выделил для него 7 байтов:источник
%zu
для печатиsize_t
Большинство из этих ответов верны, но для большей ясности ...
«Память только для чтения», на которую ссылаются люди, - это текстовый сегмент в терминах ASM. Это то же самое место в памяти, куда загружаются инструкции. Это доступно только для чтения по очевидным причинам, таким как безопасность. Когда вы создаете char *, инициализированный в строку, строковые данные компилируются в текстовый сегмент, и программа инициализирует указатель так, чтобы он указывал на текстовый сегмент. Так что, если вы попытаетесь изменить это, kaboom. Segfault.
При записи в виде массива компилятор помещает инициализированные строковые данные в сегмент данных, который находится в том же месте, где живут ваши глобальные переменные и тому подобное. Эта память изменчива, поскольку в сегменте данных нет инструкций. На этот раз, когда компилятор инициализирует массив символов (который все еще является просто символом *), он указывает на сегмент данных, а не на текстовый сегмент, который вы можете безопасно изменить во время выполнения.
источник
Тяга C99 N1256
Существует два различных варианта использования строковых литералов символов:
Инициализировать
char[]
:Это «больше волшебства», и описано в 6.7.8 / 14 «Инициализация»:
Так что это просто ярлык для:
Как и любой другой обычный массив,
c
может быть изменен.Везде: генерирует:
Поэтому, когда вы пишете:
Это похоже на:
Обратите внимание на неявное приведение от
char[]
кchar *
, которое всегда допустимо.Затем, если вы измените
c[0]
, вы также измените__unnamed
, что является UB.Это описано в 6.4.5 «Строковые литералы»:
6.7.8 / 32 «Инициализация» приводит прямой пример:
GCC 4.8 x86-64 реализация ELF
Программа:
Компилировать и декомпилировать:
Выход содержит:
Вывод: GCC хранит
char*
его в.rodata
разделе, а не в.text
.Если мы сделаем то же самое для
char[]
:мы получаем:
поэтому он хранится в стеке (относительно
%rbp
).Однако обратите внимание, что скрипт компоновщика по умолчанию помещает
.rodata
и.text
в тот же сегмент, который имеет исполняемый файл, но не имеет разрешения на запись. Это можно наблюдать с:который содержит:
источник
В первом коде «строка» является строковой константой, и строковые константы никогда не должны изменяться, поскольку они часто помещаются в постоянную память. «str» - указатель, используемый для изменения константы.
Во втором коде «строка» является инициализатором массива, своего рода сокращение для
«str» - это массив, размещенный в стеке, и его можно свободно изменять.
источник
str
является глобальным илиstatic
.Потому что тип
"whatever"
в контексте 1-го примераconst char *
(даже если вы назначаете его неконстантному символу *), это означает, что вы не должны пытаться писать в него.Компилятор ввел это в действие, поместив строку в часть памяти, доступную только для чтения, и, следовательно, запись в нее генерирует ошибку сегмента.
источник
Чтобы понять эту ошибку или проблему, вы должны сначала знать разницу между ч / б указателем и массивом, поэтому сначала я объясню вам различия ч / б
массив строк
В памяти массив хранится в непрерывных ячейках памяти, хранится как
[h][e][l][l][o][\0] =>[]
ячейка памяти размером 1 символ, и к этим ячейкам непрерывной памяти можно обращаться по имени с именем strarray здесь. Так что здесь сам строковый массивstrarray
содержит все символы строки, инициализированные в нем. случай здесь,"hello"
так что мы можем легко изменить его содержимое памяти, обращаясь к каждому символу по его значению индексаи его значение изменилось
'm'
так, что значение изменилось на"mello"
;Здесь следует отметить, что мы можем изменить содержимое массива строк, изменив символ за символом, но не можем инициализировать другую строку непосредственно к ней, например
strarray="new string"
это неверноУказатель
Как мы все знаем, указатель указывает на ячейку памяти в памяти, неинициализированный указатель указывает на произвольную ячейку памяти, а после инициализации указывает на определенную ячейку памяти.
здесь указатель ptr инициализируется строкой,
"hello"
которая является константной строкой, хранящейся в постоянном запоминающем устройстве (ПЗУ), поэтому"hello"
ее нельзя изменить, поскольку она хранится в ПЗУи ptr хранится в секции стека и указывает на постоянную строку
"hello"
поэтому ptr [0] = 'm' недопустимо, так как вы не можете получить доступ только для чтения памяти
Но ptr может быть инициализирован непосредственно к другому строковому значению, так как это просто указатель, поэтому он может указывать на любой адрес памяти переменной своего типа данных
источник
Вышеуказанные наборы
str
указывают на буквальное значение"string"
которое жестко закодировано в двоичном изображении программы, которое, вероятно, помечено как доступное только для чтения в памяти.Так
str[0]=
что пытается записать в код приложения только для чтения. Я думаю, это, вероятно, зависит от компилятора.источник
выделяет указатель на строковый литерал, который компилятор помещает в неизменяемую часть вашего исполняемого файла;
выделяет и инициализирует локальный массив, который можно изменить
источник
int *b = {1,2,3)
как мы пишемchar *s = "HelloWorld"
?Часто задаваемые вопросы о C, на которые ссылается @matli, упоминают об этом, но здесь еще никто об этом не упоминает, поэтому для пояснения: если строковый литерал (строка в двойных кавычках в вашем источнике) используется где-либо, кроме как для инициализации массива символов (то есть: @ Второй пример Марка, который работает правильно), эта строка хранится компилятором в специальной таблице статических строк , которая похожа на создание глобальной статической переменной (конечно, только для чтения), которая по сути является анонимной (имя переменной не имеет) «). Часть только для чтения - важная часть, и именно поэтому первый пример кода @ Mark содержит ошибки.
источник
int *b = {1,2,3)
как мы пишемchar *s = "HelloWorld"
?Строка определяет указатель и указывает на литеральную строку. Буквенная строка недоступна для записи, поэтому, когда вы делаете:
Вы получаете ошибку сегмента. На некоторых платформах литерал может находиться в доступной для записи памяти, поэтому вы не увидите segfault, но это неверный код (приводящий к неопределенному поведению) независимо.
Линия:
выделяет массив символов и копирует литеральную строку в этот массив, который полностью доступен для записи, поэтому последующее обновление не представляет проблем.
источник
int *b = {1,2,3)
как мы пишемchar *s = "HelloWorld"
?Строковые литералы, такие как «строка», вероятно, размещаются в адресном пространстве вашего исполняемого файла как данные только для чтения (дайте или возьмите ваш компилятор). Когда вы дотрагиваетесь до него, он пугается, что вы находитесь в его зоне купального костюма, и дает вам знать об ошибке сегмента.
В первом примере вы получаете указатель на эти постоянные данные. Во втором примере вы инициализируете массив из 7 символов с копией данных const.
источник
источник
Во-первых,
str
указатель, который указывает на"string"
. Компилятору разрешено помещать строковые литералы в места в памяти, в которые вы не можете писать, но можете только читать. (Это действительно должно быть вызвано предупреждение, так как вы назначаяconst char *
кchar *
. Разве вы отключили предупреждение, или же вы просто игнорировать их?)Во-вторых, вы создаете массив, то есть память, к которой у вас есть полный доступ, и инициализируете его
"string"
. Вы создаетеchar[7]
(шесть для букв, один для завершающего '\ 0'), и вы делаете с ним все, что захотите.источник
const
префикс делает переменныеchar [N]
нетconst char [N]
, поэтому предупреждения нет. (Вы можете изменить это в gcc хотя бы мимоходом-Wwrite-strings
.)Предположим, что строки
В первом случае, литерал должен быть скопирован, когда «а» входит в область видимости. Здесь «a» - это массив, определенный в стеке. Это означает, что строка будет создана в стеке, а ее данные скопированы из кодовой (текстовой) памяти, которая обычно доступна только для чтения (это зависит от реализации, компилятор может также поместить эти программные данные только для чтения в доступную для чтения память). ).
Во втором случае p - это указатель, определенный в стеке (локальная область) и ссылающийся на строковый литерал (данные программы или текст), хранящиеся в другом месте. Обычно изменение такой памяти не является хорошей практикой и не поощряется.
источник
Первая - это одна постоянная строка, которую нельзя изменить. Второй - это массив с инициализированным значением, поэтому его можно изменить.
источник
Ошибка сегментации возникает при попытке доступа к памяти, которая недоступна.
char *str
указатель на строку, которую нельзя изменить (причина получения ошибки по умолчанию).тогда как
char str[]
является массивом и может быть изменяемым.источник