Ошибка странной сегментации (разница между массивами и указателями?)

#c #segmentation-fault

#c #ошибка сегментации

Вопрос:

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

 void main() {
   char *str = "example string";
   wrapChrInStr(str, 'a');
}

void wrapChrInStr(char *str, unsigned char chr) {
   char *ptr = str;
   char c;

   while((c = *ptr)) {
       if(c != chr) {
           *str = c;
           str  ;
           ptr  ;
       } else {
           ptr  ;
       }
   }
   *str = '';
}
  

Комментарии:

1. str указывает на память только для чтения, *str = ''; произойдет сбой с segfault. Вы можете объявить str как char str[] = "..." или с фиксированным размером char str[50] = "...."

2. спасибо за ваш ответ. Почему это доступно только для чтения, можете ли вы объяснить, пожалуйста?

3. Потому что "example string" это строковый литерал , и они обычно хранятся в разделе, доступном только для чтения.

4. Спасибо. Я много программирую на c, и действительно странно, что я никогда не сталкивался с этим раньше.

Ответ №1:

Спасибо. Я много программирую на c, и действительно странно, что я никогда не сталкивался с этим раньше.

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

Строковые литералы

Строковый литерал объявляется с двойными кавычками, например

 "hello world"
  

Эта строка обычно хранится в разделе, доступном только для чтения. При использовании string
литералы, лучше всего объявлять переменные с const таким образом:

 const char *str = "hello world";
  

При этом вы знаете, что str указывает на ячейку памяти, доступную только для чтения, и вы не можете
манипулируйте содержимым строки. На самом деле, если вы сделаете это:

 const char *str = "hello world";
str[0] = 'H';
// or the equivalent
*str = 'H'
  

компилятор вернет ошибку, подобную этой:

 a.c:5:5: error: assignment of read-only location ‘*str’
  

что я нашел очень полезным, потому что вы не можете случайно манипулировать
содержимое, на которое указывает str .

Массивы

Если вам нужно манипулировать содержимым строки, то вам нужно сохранить строку в массиве, например

 char str[] = "hello word";
  

В этом случае компилятор знает, что строковый литерал содержит 10 символов и резервирует 11 байт (1 байт для '' — завершающего байта) для str и инициализирует массив с помощью
содержимое строкового литерала.

Здесь вы можете делать такие вещи, как

 str[0] = 'H'
  

но вы не можете получить доступ дальше 11-го байта.

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

 char str[11] = "Hello world";
  

Если вы объявите меньше места ( char str[3] = "hello world"; например),
ваш компилятор предупредит вас чем-то вроде этого

 a.c:4:14: warning: initializer-string for array of chars is too long
  

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

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

Комментарии:

1. «В этом случае размер должен быть по крайней мере таким же, как длина 1 строкового литерала». — 1 не применяется, вы даже показали это в своем примере (в инициализаторе char str[11] есть 11 ненулевых символов).

2. Да, но для правильной программы это верно. Потому что строка без символа завершения приведет к неопределенному поведению