char *strtok(char *s1, const char *s2)
repeated calls to this function break string s1 into "tokens"--that is the string is broken into substrings, each terminating with a '\0', where the '\0' replaces any characters contained in string s2. The first call uses the string to be tokenized as s1; subsequent calls use NULL as the first argument. A pointer to the beginning of the current token is returned; NULL is returned if there are no more tokens.
Привет,
Я только что пытался использовать strtok и обнаружил, что если я передаю char* в s1, я получаю ошибку сегментации. Если я передам char[], strtok будет работать нормально.
Почему это?
Я погуглил, и причина, похоже, в том, что char* доступен только для чтения, а char[] доступен для записи. Было бы очень полезно более подробное объяснение.





Чем вы инициализировали char *?
Если что-то вроде
char *text = "foobar";
тогда у вас есть указатель на некоторые символы, доступные только для чтения
Для
char text[7] = "foobar";
тогда у вас есть семиэлементный массив символов, с которым вы можете делать то, что вам нравится.
strtok записывает в строку, которую вы ей даете, заменяя символ-разделитель на null и сохраняя указатель на остальную часть строки.
Следовательно, если вы передадите ему строку только для чтения, он попытается записать в нее, и вы получите segfault.
Кроме того, поскольку strtok сохраняет ссылку на остальную часть строки, он не является реентерабельным - вы можете использовать его только для одной строки за раз. На самом деле, этого лучше избегать - вместо этого рассмотрите strsep (3) - см., Например, здесь: http://www.rt.com/man/strsep.3.html (хотя он все еще записывается в строку, поэтому имеет ту же проблему только для чтения / segfault)
Извините, если это звучит глупо, но что мешает нам сказать * (text + 3) = 'a' в символе * text = "foobar"; Версия?
Я только что попробовал. Ничто не мешает вам сделать это, кроме segfault, потому что "text + 3" по-прежнему относится к постоянной памяти.
@Paul: strsep - плохая замена strtok, он страдает от многих из тех же проблем, что и strtok, а именно, если изменяет строку и не работает со строковыми литералами.
Роберт, да, стрцеп тоже бедный. Предлагаемая альтернатива?
@Paul: простое решение - сначала сделать копию строки, чтобы убедиться, что у вас есть модифицируемая версия строки и работать с ней.
@Evan, ну да :) Я действительно интересовался, есть ли какие-нибудь стандартные библиотечные функции, чтобы помочь OP
@JJohn и @Gilbert: текст в памяти указывает на нет только для чтения. Это чушь. Вы неправильно понимаете арифметику указателей. text + 3 - это не третий символ в строке, а указатель далеко за концом (фактически 3 * sizeof (char *) байтов). Вам нужен текст [3]
Виноват стандарт 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 ++ дать мне какое-либо предупреждение о назначении литерала неконстантному char *, так что это не так уж громко устарело.
[Обновлено: aha: -Wwrite-strings, который не входит в -Wall или -Wextra]
Ключевого слова const не было в K&R C. Наличие миллионов (миллиардов?) Существующих строк char* s = "abc";, внезапно оказавшихся недействительными, несомненно, замедлило бы (если не остановило) принятие ANSI / ISO C. Даже попытки изменить его сегодня столкнулись бы с аналогичным противодействием. (как кажется, вы находите).
Это объясняет исторические причины, спасибо. Я предполагаю, что моя жалоба заключается в том, что современные компиляторы C и C++ должны предупреждать, возможно, изначально на очень высоком уровне предупреждения. Я не возражаю против нескольких предупреждений из кода эры K&R, но было бы неплохо, если бы новый код не делал этого.
Важный момент, который подразумевается, но не указывается явно:
Основываясь на вашем вопросе, я предполагаю, что вы новичок в программировании на C, поэтому я хотел бы объяснить немного больше о вашей ситуации. Простите меня, если я ошибаюсь; C может быть трудно выучить в основном из-за тонкого недопонимания основных механизмов, поэтому я предпочитаю делать вещи как можно более простыми.
Как вы знаете, когда вы пишете свою программу на C, компилятор заранее создает все для вас на основе синтаксиса. Когда вы объявляете переменную в любом месте вашего кода, например:
int x = 0;
Компилятор читает эту строку текста и говорит себе: хорошо, мне нужно заменить все вхождения в текущей области кода x постоянной ссылкой на область памяти, которую я выделил для хранения целого числа.
Когда ваша программа запущена, эта строка приводит к новому действию: мне нужно установить область памяти, которую x ссылается на значение int0.
Обратите внимание на небольшую разницу: ячейка памяти, в которой хранится контрольная точка x, постоянна (и не может быть изменена). Однако ценность, которую указывает x, может быть изменена. Вы делаете это в своем коде через присваивание, например x = 15;. Также обратите внимание, что одна строка кода фактически представляет собой две отдельные команды для компилятора.
Когда у вас есть такое утверждение:
char *name = "Tom";
Процесс компилятора выглядит следующим образом: ОК, мне нужно заменить все вхождения в текущей области кода name постоянной ссылкой на область памяти, которую я выделил для хранения значения указателя char. И так оно и есть.
Но есть второй шаг, который сводится к следующему: мне нужно создать постоянный массив символов, содержащий значения «T», «o», «m» и NULL. Затем мне нужно заменить часть кода, в которой написано "Tom", на адрес памяти этой постоянной строки.
Когда ваша программа запускается, происходит последний шаг: установка указателя на значение char (которое не является константой) на адрес памяти этой автоматически созданной строки (которая является константой является).
Таким образом, char * не доступен только для чтения. Только const char * доступен только для чтения. Но ваша проблема в этом случае не в том, что char * доступны только для чтения, а в том, что ваш указатель ссылается на области памяти, доступные только для чтения.
Я поднимаю все это, потому что понимание этой проблемы является препятствием между просмотром определения этой функции из библиотеки и самостоятельным пониманием проблемы и необходимостью спрашивать нас. И я несколько упростил некоторые детали в надежде сделать проблему более понятной.
Я надеюсь, что это было полезно. ;)
NULL (нулевой указатель) отличается от NUL (ASCII 0). Ситуация достаточно запутанная, но поскольку макрос C имеет значение NULL с двумя L, лучше (на мой взгляд) ссылаться на ASCII 0 как на NUL (или «нулевой символ»).
Вкратце:
char *s = "HAPPY DAY";
printf("\n %s ", s);
s = "NEW YEAR"; /* Valid */
printf("\n %s ", s);
s[0] = 'c'; /* Invalid */
Если вы посмотрите документацию к вашему компилятору, скорее всего, вы можете установить параметр, позволяющий сделать эти строки доступными для записи.
Итак, в версии char * указатель указывает на постоянную память. В версии char [] переменная массива находится в памяти для чтения-записи, а код инициализатора при запуске C копирует строковый литерал в массив.