Почему этот 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 по ряду причин. Вот те, которые приходят на ум
- s NULL
- s указывает на строку const, которая хранится в постоянной памяти
- 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.