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

StackOverflow https://stackoverflow.com/questions/164194

  •  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

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

    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 с типом "указатель на символ" и инициализирует его, указывая на объект с типом "массив символов" длиной 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 [] является массивом и может быть изменяемым.

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top