C strtok () и строковые литералы только для чтения

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 */ 

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