char * strtok (char * s1, const char * s2)
повторные вызовы этой функции прерывают строку s1 в «токены» – это строка разбивается на подстроки, каждая из которых заканчивается символом «\ 0», где «\ 0» заменяет любые символы, содержащиеся в строке s2. Первый вызов использует строку, обозначаемую как s1; последующие вызовы используют NULL в качестве первого аргумента. Возвращается указатель на начало текущего токена; NULL возвращается, если больше нет токенов.
Привет,
Я пытаюсь использовать strtok
только сейчас и выяснил, что если я передам char*
в s1
, я получаю ошибку сегментации. Если я передаю char[]
, strtok
отлично работает.
Почему это?
Я googled вокруг и причина, кажется, что-то о том, как char*
только для чтения, а char[]
можно записать. Более полное объяснение было бы очень оценено.
Что вы инициализировали char *
?
Если что-то вроде
char *text = "foobar";
то у вас есть указатель на некоторые символы только для чтения
За
char text[7] = "foobar";
то у вас есть семиэлементный массив символов, с которым вы можете делать то, что вам нравится.
strtok
записывает в строку, которую вы ему передаете, – перезаписывая символ разделителя null
и сохраняя указатель на остальную часть строки.
Следовательно, если вы передадите ему строку только для чтения, она попытается записать ее, и вы получите segfault.
Кроме того, поскольку strtok
сохраняет ссылку на остальную часть строки, это не reeentrant – вы можете использовать ее только по одной строке за раз. Лучше всего избегать, на самом деле – рассмотрите strsep (3) вместо этого – см., Например, здесь: http://www.rt.com/man/strsep.3.html (хотя это все еще записывается в строку, поэтому имеет одно и то же чтение – только проблема / segfault)
Важный момент, который выведен, но не указан явно:
Основываясь на вашем вопросе, я предполагаю, что вы довольно новичок в программировании на C, поэтому я хотел бы объяснить немного больше о вашей ситуации. Простите меня, если я ошибаюсь; C может быть трудно узнать в основном из-за тонкого непонимания в основных механизмах, поэтому мне нравится делать все как можно проще.
Как вы знаете, когда вы выписываете свою C-программу, компилятор предварительно создает для вас все, основываясь на синтаксисе. Когда вы объявляете переменную в любом месте вашего кода, например:
int x = 0;
Компилятор читает эту строку текста и говорит себе: ОК, мне нужно заменить все вхождения в текущей области кода x
постоянной ссылкой на область памяти, которую я выделил для хранения целого числа.
Когда ваша программа запущена, эта строка приводит к новому действию: мне нужно установить область памяти, что x
ссылается на значение int
0
.
Обратите внимание на незначительную разницу здесь: местоположение памяти, в которой находится контрольная точка x
является постоянным (и не может быть изменено). Тем не менее, значение, которое x
точек можно изменить. Вы делаете это в своем коде через назначение, например x = 15;
, Также обратите внимание, что одна строка кода фактически составляет две отдельные команды компилятору.
Когда у вас есть заявление вроде:
char *name = "Tom";
Процесс компилятора выглядит так: ОК, мне нужно заменить все вхождения в текущей области кода name
постоянной ссылкой на область памяти, которую я выделил для хранения значения указателя char
. И это так.
Но есть второй шаг, который сводится к следующему: мне нужно создать постоянный массив символов, который содержит значения «T», «o», «m» и NULL
. Затем мне нужно заменить часть кода, которая говорит "Tom"
с адресом памяти этой константной строки.
Когда ваша программа запущена, наступает последний шаг: установка указателя на значение char
(которое не является константой) на адрес памяти автоматически созданной строки (которая является постоянной).
Таким образом, char *
не доступен только для чтения. Только const char *
доступен только для чтения. Но ваша проблема в этом случае заключается не в том, что char *
s доступны только для чтения, а в том, что ваш указатель ссылается на области памяти только для чтения.
Я приношу все это, потому что понимание этой проблемы является барьером между вами, когда вы смотрите на определение этой функции из библиотеки и сами понимаете проблему, или спрашиваете нас. И я несколько упростил некоторые детали в надежде сделать проблему более понятной.
Я надеюсь, что это было полезно. 😉
Я обвиняю стандарт C.
char *s = "abc";
можно было бы определить, чтобы дать ту же ошибку, что и
const char *cs = "abc"; char *s = cs;
на том основании, что строковые литералы не поддаются изменению. Но это не так, это было определено для компиляции. Идите фигуру. [Редактировать: Майк Б разобрался – «const» вообще не существовало в K & R C. ISO C, а также каждая версия C и C ++ с тех пор, хотела быть обратно совместимой. Так оно и должно быть.]
Если бы было определено, чтобы дать ошибку, то вы не могли бы дойти до segfault, потому что первый параметр strtok – char *, поэтому компилятор помешал бы вам передать указатель, сформированный из литерала.
Может показаться интересным, что когда-то был план на C ++, чтобы это было устаревшим ( http://www.open-std.org/jtc1/sc22/wg21/docs/papers/1996/N0896.asc ). Но 12 лет спустя я не могу убедить gcc или g ++ дать мне какое-либо предупреждение о назначении литерала не-const char *, поэтому он не является настолько громким, что не рекомендуется.
[Edit: aha: -Wwrite-строки, которые не включены в -Wall или -Wextra]
Вкратце:
char *s = "HAPPY DAY"; printf("\n %s ", s); s = "NEW YEAR"; /* Valid */ printf("\n %s ", s); s[0] = 'c'; /* Invalid */
Если вы посмотрите на свою компиляторную документацию, есть вероятность, что вы можете установить, чтобы эти строки были доступны для записи.