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

264

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

  • 2
    MSVC выдает «Запись о нарушении прав доступа 0x ...». Я считаю, что ошибки сегментации характерны для платформ Linux / UNIX.
  • 1
    Это забавно, но на самом деле это прекрасно компилируется и работает при использовании компилятора Windows (cl) в командной строке разработчика Visual Studio. Я запутался на несколько мгновений ...
Показать ещё 1 комментарий
Теги:
c-strings
segmentation-fault

17 ответов

198
Лучший ответ

См. C FAQ, Вопрос 1.32

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

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

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

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

  • 16
    K & R раздел 5.5 ... Глупый я, должен был открыть книгу, прежде чем задавать глупые вопросы!
  • 7
    Пара других моментов: (1) segfault происходит, как описано, но его возникновение является функцией среды выполнения; если тот же код был во встроенной системе, запись может не иметь никакого эффекта, или это может фактически изменить s на z. (2) Поскольку строковые литералы недоступны для записи, компилятор может сэкономить место, поместив два экземпляра «string» в одном месте; или, если где-то еще в коде у вас есть «другая строка», то один кусок памяти может поддерживать оба литерала. Понятно, что если бы в коде было разрешено изменять эти байты, могли возникнуть странные и сложные ошибки.
Показать ещё 5 комментариев
91

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

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

printf("%p", str);

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

printf("%d", sizeof(str));
  • 11
    Всякий раз, когда вы используете «% p» в printf, вы должны привести указатель к void *, как в printf («% p», (void *) str); При печати size_t с помощью printf вы должны использовать "% zu", если используете последний стандарт C (C99).
  • 3
    Кроме того, скобки с sizeof необходимы только при получении размера типа (тогда аргумент выглядит как приведение). Помните, что sizeof - это оператор, а не функция.
Показать ещё 1 комментарий
31

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

"Постоянная память", на которую люди ссылаются, является текстовым сегментом в терминах ASM. Это то же место в памяти, где загружаются инструкции. Это доступно только для чтения по очевидным причинам, таким как безопасность. Когда вы создаете char *, инициализированный для строки, строковые данные скомпилируются в текстовый сегмент, и программа инициализирует указатель для указания в текстовый сегмент. Поэтому, если вы попытаетесь изменить его, kaboom. Segfault.

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

  • 0
    Но не правда ли, что могут быть реализации, которые позволяют изменять «постоянную память»?
19

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

C99 N1256 осадка

Существует два различных варианта использования строковых литералов символов:

  1. Инициализировать char[]:

    char c[] = "abc";      
    

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

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

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

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

    Как и любой другой обычный массив, 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', '\0' },
t[] = { 'a', 'b', 'c' };

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

char *p = "abc";

определяет p с типом "pointer to char" и инициализирует его, чтобы он указывал на объект с типом "array of char" длиной 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 в .rodata и тот же сегмент, который имеет исполняемый файл, но не имеет разрешения на запись. Это можно наблюдать с помощью:

readelf -l a.out

который содержит:

 Section to Segment mapping:
  Segment Sections...
   02     .text .rodata
  • 0
    будет работать int*b = {1,2,3} ?
17

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

Во втором коде "строка" представляет собой инициализатор массива, короткую руку для

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

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

  • 0
    В стеке или сегменте данных, если str является глобальным или static .
12

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

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

8

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

массив строк

 char strarray[] = "hello";

В массиве памяти хранятся ячейки непрерывной памяти, которые хранятся как [h][e][l][l][o][\0] =>[] - это ячейка памяти размера байта размером 1 char, а для этих ячеек непрерывной памяти можно получить доступ по имени 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 хранится в секции стека и указывает на константу string "hello"

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

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

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

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

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

6

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

  • 0
    мы можем написать int *b = {1,2,3) как мы пишем char *s = "HelloWorld" ?
6
char *str = "string";

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

char str[] = "string";

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

  • 0
    мы можем написать int *b = {1,2,3) как мы пишем char *s = "HelloWorld" ?
4

 char *str = "string";
Строка

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

  str[0] = 'z';

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

Строка:

char str[] = "string";

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

  • 0
    мы можем написать int *b = {1,2,3) как мы пишем char *s = "HelloWorld" ?
3

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

В первом примере вы получаете указатель на данные const. Во втором примере вы инициализируете массив из 7 символов с копией данных const.

2
// 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. 
1

Во-первых, str - указатель, который указывает на "string". Компилятору разрешено помещать строковые литералы в места, которые вы не можете записать, но можете читать только. (Это действительно должно было вызвать предупреждение, поскольку вы назначаете const char * для char *. У вас есть предупреждения отключены или вы просто проигнорировали их?)

Во-вторых, вы создаете массив, который является памятью, к которой у вас есть полный доступ, и инициализируя ее с помощью "string". Вы создаете char[7] (шесть для букв, один для завершения "\ 0" ), и вы делаете с ним все, что вам нравится.

  • 0
    C также поддерживает const?
  • 0
    будет работать int*b = {1,2,3} ?
Показать ещё 2 комментария
0

Предположим, что строки

char a[] = "string literal copied to stack";
char *p  = "string literal referenced by p";

В первом случае, литерал должен быть скопирован, когда "а" входит в область видимости. Здесь "a" - это массив, определенный в стеке. Это означает, что строка будет создана в стеке, а ее данные скопированы из кодовой (текстовой) памяти, которая обычно доступна только для чтения (это зависит от реализации, компилятор может также поместить эти программные данные только для чтения в доступную для чтения память).).

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

-1

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

-1

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

char *str - это указатель на строку, которая не модифицируется (причина получения seg fault).

тогда как char str[] является массивом и может быть модифицируемым.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню