Почему этот код разворота строки 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, вызывающие ошибку сегментации. Я понимаю, что могут быть лучшие способы сделать это, но мне интересно узнать, что именно в моем коде вызывает ошибку сегментации.

Обновление. Я включил функцию вызова в соответствии с запросом.

Ответ 1

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

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

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

Передайте указатель на изменяемую строку вместо

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");

Ответ 2

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

  • s имеет значение NULL
  • s указывает на константную строку, которая хранится в памяти только для чтения.
  • s не завершено NULL

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

EDIT

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

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

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

Ответ 3

Вы тестируете это что-то вроде этого?

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

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

Ответ 4

Ваше выражение совершенно неверно:

char* s = "teststring";

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

Но с:

char s[] = "teststring";

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

Ответ 5

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

Удачи.

Ответ 6

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

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

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

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

Ответ:

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

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

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

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

(акцент мой)

См. Также " Назад к основам " Джоэла.

Ответ 7

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

Ответ 8

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

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