Почему этот C-код обращения строки вызывает ошибку сегментации?

Я пытаюсь написать код, чтобы перевернуть строку на месте (я просто пытаюсь стать лучше в программировании на C и манипулировании указателями), но я не могу понять, почему я получаю ошибку сегментации:

#include <string.h>

void reverse(char *s);

int main() {
    char* s = "teststring";
    reverse(s);

    return 0;
}

void reverse(char *s) {
    int i, j;
    char temp;

    for (i=0,j = (strlen(s)-1); i < j; i++, j--) {
        temp = *(s+i);     //line 1
        *(s+i) = *(s+j);   //line 2
        *(s+j) = temp;     //line 3
    }
}

Это строки 2 и 3, которые вызывают ошибку сегментации. Я понимаю, что могут быть лучшие способы сделать это, но мне интересно узнать, что именно в моем коде вызывает ошибку сегментации.

Обновление: я включил функцию вызова в соответствии с просьбой.

8 ответов

Решение

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

Как вы называете свою функцию?

Добавлено: Вы передаете указатель на строковый литерал. Строковые литералы не модифицируются. Вы не можете перевернуть строковый литерал.

Вместо этого передайте указатель на изменяемую строку

char s[] = "teststring";
reverse(s); 

Это уже здесь было объяснено до смерти. "teststring" является строковым литералом. Строковый литерал сам по себе является неизменяемым объектом. На практике компиляторы могут (и будут) помещать его в постоянную память. Когда вы инициализируете указатель, как это

char *s = "teststring";

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

const char *s = "teststring";

Но когда вы объявляете s как

char s[] = "teststring";

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

По сути, последнее объявление функционально эквивалентно

char s[11];
strcpy(s, "teststring");

Ваш код может быть segfaulting по ряду причин. Вот те, которые приходят на ум

  1. s NULL
  2. s указывает на строку const, которая хранится в постоянной памяти
  3. s не NULL прекращается

Я думаю, что № 2 является наиболее вероятным. Можете ли вы показать нам сайт обратного вызова?

РЕДАКТИРОВАТЬ

Исходя из вашего примера № 2, безусловно, ответ. Строковый литерал в C/C++ не модифицируется. Правильный тип на самом деле const char* и не char*, Что вам нужно сделать, это передать изменяемую строку в этот буфер.

Быстрый пример:

char* pStr = strdup("foobar");
reverse(pStr);
free(pStr);

Вы тестируете это как то так?

int main() {
    char * str = "foobar";
    reverse(str);
    printf("%s\n", str);
}

Это делает строку строковым литералом, и вы, вероятно, не сможете его редактировать (segfaults для меня). Если вы определите char * str = strdup(foobar) это должно работать нормально (делает для меня).

Ваше заявление совершенно неверно:

char* s = "teststring";

"testtring" хранится в сегменте кода, который доступен только для чтения, как и код. И, s это указатель на "testtring", в то же время вы пытаетесь изменить значение диапазона памяти только для чтения. Таким образом, ошибка сегментации.

Но с:

char s[] = "teststring";

s инициализируется с помощью "testtring", которая, конечно, находится в сегменте кода, но в этом случае в стек выполняется дополнительная операция копирования.

Смотрите вопрос 1.32 в списке часто задаваемых вопросов C:

В чем разница между этими инициализациями?

char a[] = "string literal";
char *p  = "string literal";

Моя программа падает, если я пытаюсь присвоить новое значение p[i],

Ответ:

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

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

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

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

(акцент мой)

Смотрите также Назад к основам Джоэла.

Как некоторые из ответов, приведенных выше, строковая память доступна только для чтения. Тем не менее, некоторые компиляторы предоставляют возможность компиляции с записываемыми строками. Например, с gcc, Версии 3.x поддерживаются -fwritable-strings но более новые версии не делают.

Какой компилятор и отладчик вы используете? Используя gcc и gdb, я скомпилировал код с флагом -g и затем запустил его в gdb. Когда это происходит с ошибками, я просто выполняю обратную трассировку (команда bt в gdb) и вижу, какая строка вызывает ошибку. Кроме того, я просто запускаю код шаг за шагом, одновременно следя за значениями указателей в gdb, и узнаю, где именно проблема.

Удачи.

Я думаю strlen не может работать, так как s не NULL прекращается. Таким образом, поведение вашего для итерации не то, что вы ожидаете. Так как результат strlen будет больше, чем длина s, вы будете писать в память, где вы не должны быть.

Кроме того, s указывает на постоянные строки, хранящиеся в постоянной памяти. Вы не можете изменить его. Попробуйте инициализировать с помощью функции gets, как это делается в примере с strlen.

Другие вопросы по тегам