Почему я получаю ошибку сегментации при записи в строку, инициализированную “char * s”, но не “char s[]"?
-
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 и GCC.
Решение
См. часто задаваемые вопросы по C, Вопрос 1.32
Q . В чем разница между этими инициализациями?
char a [] = " строковый литерал " ;;
char * p = " строковый литерал " ;;
Моя программа падает, если я пытаюсь присвоить новое значениеp [i]
.A : строковый литерал (формальный термин для строки в двойных кавычках в C источник) может быть использован в два слегка по-разному:
<Ол>Как инициализатор для массива char, как в объявлении char a []
, он определяет начальные значения символов в этом массиве (и, при необходимости его размер).В любом другом месте он превращается в безымянный статический массив символов, и этот безымянный массив может быть сохранен в постоянной памяти, и который поэтому не может быть обязательно модифицирована. В контексте выражения массив конвертируется сразу в указатель, как обычно (см. раздел 6), так второе объявление инициализирует р указать на первый безымянный массив элемент. Ол>Некоторые компиляторы имеют переключатель контролировать ли строковые литералы доступны для записи или нет (для компиляции старых код), а некоторые могут иметь варианты заставить строковые литералы быть формально обрабатываются как массивы const char (для лучше ловить ошибки).
Другие советы
Обычно строковые литералы хранятся в постоянной памяти только при запуске программы. Это предотвращает случайное изменение строковой константы. В вашем первом примере " string "
хранится в постоянной памяти, а * str
указывает на первый символ. Ошибка происходит, когда вы пытаетесь изменить первый символ на 'z'
.
Во втором примере строка " string "
копируется компилятором из его дома, доступного только для чтения, в str []
массив. Тогда изменение первого символа разрешено. Вы можете проверить это, напечатав адрес каждого:
printf("%p", str);
Кроме того, печать размера str
во втором примере покажет вам, что компилятор выделил для него 7 байтов:
printf("%d", sizeof(str));
Большинство из этих ответов верны, но для большей ясности ...
" только для чтения памяти " люди обращаются к текстовому сегменту в терминах ASM. Это то же самое место в памяти, куда загружаются инструкции. Это доступно только для чтения по очевидным причинам, таким как безопасность. Когда вы создаете char *, инициализированный в строку, строковые данные компилируются в текстовый сегмент, и программа инициализирует указатель так, чтобы он указывал на текстовый сегмент. Так что, если вы попытаетесь изменить это, kaboom. Segfault.
При записи в виде массива компилятор помещает инициализированные строковые данные в сегмент данных, который находится в том же месте, где живут ваши глобальные переменные и тому подобное. Эта память изменчива, поскольку в сегменте данных нет инструкций. На этот раз, когда компилятор инициализирует массив символов (который все еще является просто символом *), он указывает на сегмент данных, а не на текстовый сегмент, который можно смело изменять во время выполнения.
Почему я получаю ошибку сегментации при записи в строку?
Осадка C99 N1256
Существует два различных варианта использования символьных строковых литералов:
Инициализировать
char[]
:char c[] = "abc";
Это "еще одно волшебство", описанное в разделе 6.7.8 /14 "Инициализация".:
Массив символьного типа может быть инициализирован символьным строковым литералом, опционально заключенный в фигурные скобки.Последовательные символы символьного строкового литерала (включая завершающий нулевой символ, если есть место или если массив неизвестного размера) инициализируют элементы массива.
Так что это всего лишь короткий путь для:
char c[] = {'a', 'b', 'c', '\0'};
Как и любой другой обычный массив,
c
может быть изменен.Везде еще:это генерирует:
- безымянный
- массив символов Каков тип строковых литералов в 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', '\0' }, t[] = { 'a', 'b', 'c' };
Содержимое массивов может быть изменено.С другой стороны, декларация
char *p = "abc";
определяет
p
с типом "указатель на символ" и инициализирует его, указывая на объект с типом "массив символов" длиной 4, элементы которого инициализируются символьным строковым литералом.Если будет предпринята попытка использоватьp
чтобы изменить содержимое массива, поведение не определено.
Реализация GCC 4.8 x86-64 ELF
Программа:
#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
Заключение:Магазины GCC 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
В первом коде "string" - это строковая константа, а строковые константы никогда не следует изменять, поскольку они часто помещаются в память, доступную только для чтения."str" - это указатель, используемый для изменения константы.
Во втором коде "string" - это инициализатор массива, своего рода сокращение для
char str[7] = { 's', 't', 'r', 'i', 'n', 'g', '\0' };
"str" - это массив, выделенный в стеке, и его можно свободно изменять.
Поскольку тип " независимо от "
в контексте первого примера - const char *
(даже если вы назначаете его неконстантному char *) Это означает, что вы не должны пытаться писать в него.
Компилятор ввел это в действие, поместив строку в часть памяти, доступную только для чтения, и, следовательно, запись в нее генерирует ошибку segfault.
Чтобы понять эту ошибку или проблему, вы должны сначала знать разницу между указателем и массивом. так вот, во-первых, я должен объяснить вам различия ч / б их
строковый массив
char strarray[] = "hello";
В памяти массив хранится в ячейках с непрерывной памятью и хранится как [h] [e] [l] [l] [o] [\ 0] = > []
, равный 1 байту ячейка памяти размера, и эта непрерывная ячейка памяти может быть доступна по имени с именем strarray здесь. поэтому здесь сам строковый массив strarray
, содержащий все символы строки, инициализированные для него. в этом случае здесь " привет " код>
так что мы можем легко изменить его содержимое памяти, получая доступ к каждому символу по его значению индекса
`strarray[0]='m'` it access character at index 0 which is 'h'in strarray
и его значение изменилось на 'm'
, поэтому значение страррей изменилось на " mello "
;
Здесь следует отметить, что мы можем изменить содержимое массива строк, изменив символ за символом, но не можем инициализировать другую строку непосредственно к нему, например, strarray = " новая строка "
недопустима р>
Указатель
Как мы все знаем, указатель указывает на область памяти в памяти, неинициализированный указатель указывает на случайную ячейку памяти, а после инициализации указывает на определенную ячейку памяти
char *ptr = "hello";
здесь указатель ptr инициализируется строкой "hello"
, которая является константной строкой, хранящейся в постоянной памяти (ПЗУ), поэтому " hello &
и ptr хранятся в секции стека и указывают на постоянную строку " здравствуйте "
поэтому ptr [0] = 'm' недопустимо, так как вы не можете получить доступ к памяти только для чтения
Но ptr можно инициализировать другим строковым значением напрямую, так как это просто указатель, поэтому он может указывать на любой адрес памяти переменной своего типа данных
ptr="new string"; is valid
char *str = "string";
Выше указано, что str
указывает на буквальное значение " string "
, которое жестко закодировано в двоичном изображении программы, которое, вероятно, помечено как доступное только для чтения. в памяти. Р>
Итак, str [0] =
пытается записать код приложения только для чтения. Я думаю, что это, вероятно, зависит от компилятора.
char *str = "string";
выделяет указатель на строковый литерал, который компилятор помещает в неизменяемую часть вашего исполняемого файла;
char str[] = "string";
выделяет и инициализирует локальный массив, который можно изменить
В FAQ C, на который ссылается @matli, упоминается об этом, но здесь еще никто об этом не упоминает, поэтому для пояснения: если строковый литерал (строка в двойных кавычках в вашем источнике) используется где-либо , кроме чтобы инициализировать массив символов (т. е. второй пример @ Mark, который работает правильно), эта строка сохраняется компилятором в специальной таблице статических строк , которая похожа на создание глобальной статической переменной ( Разумеется, только для чтения), который по существу является анонимным (не имеет переменной "name"). Часть только для чтения - это важная часть, и именно поэтому в первом примере кода @ Mark есть ошибки.
char *str = "string";
Строка определяет указатель и указывает на литеральную строку. Буквенная строка недоступна для записи, поэтому, когда вы делаете:
str[0] = 'z';
вы получаете ошибку сегмента. На некоторых платформах литерал может находиться в доступной для записи памяти, поэтому вы не увидите segfault, но это неверный код (приводящий к неопределенному поведению) независимо.
Строка:
char str[] = "string";
выделяет массив символов и копирует литеральную строку в этот массив, который полностью доступен для записи, поэтому последующее обновление не вызывает проблем.
Строковые литералы, такие как " строка " вероятно, расположены в адресном пространстве вашего исполняемого файла как данные только для чтения (дайте или возьмите ваш компилятор). Когда вы дотрагиваетесь до него, он пугается, что вы находитесь в зоне купального костюма, и сообщает вам об ошибке сегмента.
В первом примере вы получаете указатель на эти постоянные данные. Во втором примере вы инициализируете массив из 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» - это массив, определенный в стеке. Это означает, что строка будет создана в стеке, и ее данные будут скопированы из кодовой (текстовой) памяти, которая обычно доступна только для чтения (это зависит от реализации, компилятор может также поместить эти программные данные только для чтения в доступную для чтения память). ). р>
Во втором случае p - это указатель, определенный в стеке (локальная область) и ссылающийся на строковый литерал (данные или текст программы), хранящиеся в другом месте. Обычно изменение такой памяти не является хорошей практикой и не поощряется.
Первый - это одна постоянная строка, которую нельзя изменить. Второй - это массив с инициализированным значением, поэтому его можно изменить. Р>
возникает, когда вы обращаетесь к памяти, которая недоступна. Р>
char * str
- указатель на строку, которая не может быть изменена (причина получения ошибки сегмента) ..
тогда как char str []
является массивом и может быть изменяемым.