Следующий код получает ошибку 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.





Поскольку тип "whatever" в контексте 1-го примера - const char * (даже если вы назначаете его неконстантному символу *), это означает, что вам не следует пытаться писать в него.
Компилятор добился этого, поместив строку в доступную только для чтения часть памяти, поэтому запись в нее генерирует segfault.
Обычно строковые литералы сохраняются в постоянной памяти при запуске программы. Это сделано для предотвращения случайного изменения строковой константы. В вашем первом примере "string" хранится в постоянной памяти, а *str указывает на первый символ. Ошибка сегментации возникает, когда вы пытаетесь изменить первый символ на 'z'.
Во втором примере строка "string" - это скопировано компилятора из своего дома, доступного только для чтения, в массив str[]. Тогда разрешается изменение первого символа. Вы можете проверить это, распечатав адрес каждого:
printf("%p", str);
Кроме того, печать размера str во втором примере покажет вам, что компилятор выделил для него 7 байтов:
printf("%d", sizeof(str));
Всякий раз, когда вы используете "% p" в printf, вы должны привести указатель к void *, как в printf ("% p", (void *) str); При печати size_t с помощью printf вы должны использовать "% zu", если используете последний стандарт C (C99).
Кроме того, круглые скобки с sizeof необходимы только при выборе размера типа (тогда аргумент выглядит как приведение). Помните, что sizeof - это оператор, а не функция.
и используйте %zu для печати size_t
предупреждение: неизвестный символ типа преобразования 'z' в формате [-Wformat =]: /
char *str = "string";
Вышеуказанное устанавливает str для указания на буквальное значение "string", которое жестко закодировано в двоичном образе программы, который, вероятно, помечен как доступный только для чтения в памяти.
Итак, str[0]= пытается писать в код приложения, доступный только для чтения. Я бы предположил, что это, вероятно, зависит от компилятора.
В первом коде «строка» - это строковая константа, и строковые константы никогда не должны изменяться, потому что они часто помещаются в постоянную память. "str" - это указатель, используемый для изменения константы.
Во втором коде строка - это инициализатор массива, что-то вроде сокращения для
char str[7] = { 's', 't', 'r', 'i', 'n', 'g', '\0' };
"str" - это массив, размещенный в стеке, который можно свободно изменять.
В стеке или сегменте данных, если str является глобальным или static.
char *str = "string";
выделяет указатель на строковый литерал, который компилятор помещает в неизменяемую часть вашего исполняемого файла;
char str[] = "string";
выделяет и инициализирует локальный массив, который можно изменять
мы можем написать int *b = {1,2,3), как мы пишем char *s = "HelloWorld"?
Строковые литералы, такие как «строка», вероятно, размещены в адресном пространстве вашего исполняемого файла как данные только для чтения (плюс-минус ваш компилятор). Когда вы собираетесь прикоснуться к нему, он пугается, что вы находитесь в зоне купального костюма, и сообщает вам об этом с ошибкой.
В вашем первом примере вы получаете указатель на эти константные данные. Во втором примере вы инициализируете массив из 7 символов копией константных данных.
В
char *str = "string";
line определяет указатель и указывает на буквальную строку. Буквальная строка недоступна для записи, поэтому, когда вы это сделаете:
str[0] = 'z';
вы получаете ошибку сегмента. На некоторых платформах литерал может находиться в доступной для записи памяти, поэтому вы не увидите segfault, но это недопустимый код (что приводит к неопределенному поведению) в любом случае.
Линия:
char str[] = "string";
выделяет массив символов и копии литеральную строку в этот массив, который полностью доступен для записи, поэтому последующее обновление не проблема.
мы можем написать int *b = {1,2,3), как мы пишем char *s = "HelloWorld"?
См. FAQ по C, Вопрос 1.32
Q: What is the difference between these initializations?
char a[] = "string literal";char *p = "string literal";
My program crashes if I try to assign a new value top[i].A: A string literal (the formal term for a double-quoted string in C source) can be used in two slightly different ways:
- As the initializer for an array of char, as in the declaration of
char a[], it specifies the initial values of the characters in that array (and, if necessary, its size).- Anywhere else, it turns into an unnamed, static array of characters, and this unnamed array may be stored in read-only memory, and which therefore cannot necessarily be modified. In an expression context, the array is converted at once to a pointer, as usual (see section 6), so the second declaration initializes p to point to the unnamed array's first element.
Some compilers have a switch controlling whether string literals are writable or not (for compiling old code), and some may have options to cause string literals to be formally treated as arrays of const char (for better error catching).
Пара других моментов: (1) segfault происходит, как описано, но его возникновение является функцией среды выполнения; если такой же код был во встроенной системе, запись может не иметь никакого эффекта или может фактически изменить s на z. (2) Поскольку строковые литералы не доступны для записи, компилятор может сэкономить место, поместив два экземпляра строки в одно и то же место; или, если где-то еще в коде есть «другая строка», то один фрагмент памяти может поддерживать оба литерала. Ясно, что если бы коду было разрешено изменять эти байты, могли бы возникнуть странные и сложные ошибки.
@greggo: Хорошее замечание. Также есть способ сделать это в системах с MMU, используя mprotect для активации защиты только для чтения (см. здесь).
Итак, char * p = "blah" на самом деле создает временный массив?
И через 2 года написания на C++ ... TIL
@rahultyagi, что ты имеешь в виду?
@rahul tyagi, не временный массив. Напротив, это самый долгоживущий из массивов. Он создается компилятором и находится в самом исполняемом файле. Из вышесказанного вы должны были понять, что это массив общий, который следует рассматривать как только чтение (и на самом деле он может быть доступен только для чтения).
Прежде всего, str - это указатель, указывающий на "string". Компилятору разрешено помещать строковые литералы в те места в памяти, которые вы не можете записывать, но можете только читать. (Это действительно должно было вызвать предупреждение, поскольку вы назначаете const char * на char *. Вы отключили предупреждения или просто проигнорировали их?)
Во-вторых, вы создаете массив, представляющий собой память, к которой у вас есть полный доступ, и инициализируете его с помощью "string". Вы создаете char[7] (шесть для букв, один для завершающего '\ 0') и делаете с ним все, что хотите.
@ Ферруччо,? Да префикс const делает переменные доступными только для чтения
В строковых литералах C тип char [N], а не const char [N], поэтому предупреждения нет. (Вы можете изменить это в gcc, по крайней мере, передав -Wwrite-strings.)
В часто задаваемых вопросах C, на который ссылается @matli, упоминается это, но здесь еще нет никого, поэтому для пояснения: если строковый литерал (строка в двойных кавычках в вашем источнике) используется где-либо, Кроме как для инициализации массива символов (например: второй пример, который работает правильно), эта строка сохраняется компилятором в специальном статическая таблица строк, что сродни созданию глобальной статической переменной (конечно, только для чтения), которая по сути является анонимной (не имеет «имени» переменной). Часть только чтение является важной частью, и именно поэтому в первом примере кода @Mark происходит сбой.
мы можем написать int *b = {1,2,3), как мы пишем char *s = "HelloWorld"?
Большинство из этих ответов верны, но просто для большей ясности ...
«Постоянная память для чтения», о которой говорят люди, - это текстовый сегмент в терминах ASM. Это то же самое место в памяти, куда загружаются инструкции. Он доступен только для чтения по очевидным причинам, таким как безопасность. Когда вы создаете char *, инициализированный строкой, строковые данные компилируются в текстовый сегмент, и программа инициализирует указатель, чтобы он указывал на текстовый сегмент. Так что, если вы попытаетесь это изменить, бахвальф. Segfault.
При записи в виде массива компилятор вместо этого помещает инициализированные строковые данные в сегмент данных, который находится в том же месте, что и ваши глобальные переменные и тому подобное. Эта память изменяема, поскольку в сегменте данных нет инструкций. На этот раз, когда компилятор инициализирует массив символов (который по-прежнему является просто символом *), он указывает на сегмент данных, а не на текстовый сегмент, который вы можете безопасно изменить во время выполнения.
Но разве не могут быть реализации, позволяющие изменять «постоянную память»?
При записи в виде массива компилятор помещает инициализированные строковые данные в сегмент данных, если они статические или глобальные. В противном случае (например, для обычного автоматического массива) он помещается в стек, в кадр стека функции main. Правильный?
@SE Да, я бы предположил, что Боб Сомерс имеет в виду и стек, и кучу, и статические (включая статические и глобальные переменные) при записи «сегмента данных». А в стек кладется локальный массив, значит, в этом вы правы :)
Извините, но вы, вероятно, правы здесь. Сегмент данных - это часть памяти, выделенная для инициализированных глобальных или статических переменных, но массив также может быть помещен в стек, если он локальный, как вы написали.
// 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.
Ошибка сегментации возникает, когда вы пытаетесь получить доступ к недоступной памяти.
char *str - это указатель на строку, которую нельзя изменить (причина получения segfault).
тогда как char str[] является массивом и может быть изменен ..
Во-первых, это одна постоянная строка, которую нельзя изменить. Во-вторых, это массив с инициализированным значением, поэтому его можно изменить.
Чтобы понять эту ошибку или проблему, вы должны сначала узнать разницу между указателем и массивом. Итак, сначала я объясню вам различия между ними
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 может быть инициализирован другим строковым значением напрямую, поскольку это просто указатель, поэтому он может указывать на любой адрес памяти переменной своего типа данных.
ptr = "new string"; is valid
Why do I get a segmentation fault when writing to a string?
C99 N1256 тяга
Есть два разных использования литералов символьной строки:
Инициализировать char[]:
char c[] = "abc";
Это «больше волшебства», описанное в 6.7.8 / 14 «Инициализация»:
An array of character type may be initialized by a character string literal, optionally enclosed in braces. Successive characters of the character string literal (including the terminating null character if there is room or if the array is of unknown size) initialize the elements of the array.
Так что это просто ярлык для:
char c[] = {'a', 'b', 'c', '\0'};
Как и любой другой обычный массив, c можно модифицировать.
В другом месте: он генерирует:
Итак, когда вы пишете:
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 In translation phase 7, a byte or code of value zero is appended to each multibyte character sequence that results from a string literal or literals. The multibyte character sequence is then used to initialize an array of static storage duration and length just sufficient to contain the sequence. For character string literals, the array elements have type char, and are initialized with the individual bytes of the multibyte character sequence [...]
6 It is unspecified whether these arrays are distinct provided their elements have the appropriate values. If the program attempts to modify such an array, the behavior is undefined.
6.7.8 / 32 "Инициализация" дает прямой пример:
EXAMPLE 8: The declaration
char s[] = "abc", t[3] = "abc";defines "plain" char array objects
sandtwhose elements are initialized with character string literals.This declaration is identical to
char s[] = { 'a', 'b', 'c', '\0' }, t[] = { 'a', 'b', 'c' };The contents of the arrays are modifiable. On the other hand, the declaration
char *p = "abc";defines
pwith type "pointer to char" and initializes it to point to an object with type "array of char" with length 4 whose elements are initialized with a character string literal. If an attempt is made to usepto modify the contents of the array, the behavior is undefined.
Реализация 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
Предположим, что строки:
char a[] = "string literal copied to stack";
char *p = "string literal referenced by p";
В первом случае литерал должен быть скопирован, когда "a" входит в область видимости. Здесь «a» - это массив, определенный в стеке. Это означает, что строка будет создана в стеке, и ее данные будут скопированы из памяти кода (текста), которая обычно доступна только для чтения (это зависит от реализации, компилятор может также поместить эти данные программы только для чтения в память с возможностью чтения и записи. ).
Во втором случае p - это указатель, определенный в стеке (локальная область видимости) и ссылающийся на строковый литерал (данные программы или текст), хранящийся в другом месте. Обычно изменение такой памяти не является хорошей практикой и не поощряется.
Это забавно, но на самом деле он компилируется и отлично работает при использовании компилятора Windows (cl) в командной строке разработчика Visual Studio. На несколько мгновений сбил меня с толку ...