C's strtok () и строковые литералы только для чтения - PullRequest
4 голосов
/ 07 ноября 2008

char * strtok (char * s1, const char * s2)

повторные вызовы этой функции разбивают строку s1 на "токены" - то есть строка разбита на подстроки, каждая оканчивается на '\ 0', где '\ 0' заменяет любые символы содержится в строке s2. Первый звонок использует строку для токенизации как s1; последующие вызовы используют NULL в качестве первого аргумент. Указатель на начало текущего токена возвращается; НОЛЬ возвращается, если больше нет лексемы.

Привет,

Я пытался использовать strtok только сейчас и обнаружил, что если я передам char* в s1, я получу ошибку сегментации. Если я передаю char[], strtok работает нормально.

Почему это?

Я погуглил, и причина, по-видимому, в том, что char* только для чтения и char[] для записи. Было бы очень полезно получить более подробное объяснение.

Ответы [ 5 ]

14 голосов
/ 07 ноября 2008

Для чего вы инициализировали char *?

Если что-то вроде

char *text = "foobar";

тогда у вас есть указатель на некоторые символы только для чтения

Для

char text[7] = "foobar";

тогда у вас есть массив из семи элементов, с которым вы можете делать то, что вам нравится.

strtok записывает в заданную вами строку - перезаписывает символ разделителя с помощью null и сохраняет указатель на остальную часть строки.

Следовательно, если вы передадите ей строку только для чтения, она попытается записать в нее, и вы получите ошибку segfault.

Кроме того, поскольку strtok хранит ссылку на остальную часть строки, она не реентерабельна - вы можете использовать ее только для одной строки за раз. На самом деле этого лучше избегать - рассмотрите вместо этого strsep (3) - см., Например, здесь: http://www.rt.com/man/strsep.3.html (хотя это все еще записывает в строку, поэтому имеет ту же проблему только для чтения / segfault)

5 голосов
/ 08 ноября 2008

Важный момент, который выводится, но не указывается явно:

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

Я поднимаю все это, потому что понимание этой проблемы - это барьер между тем, как вы смотрите на определение этой функции из библиотеки и сами понимаете проблему, а не спрашиваете нас. И я несколько упростил некоторые детали в надежде сделать проблему более понятной.

Надеюсь, это было полезно. ;)

2 голосов
/ 07 ноября 2008

Я виню стандарт С.

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 /apers / 1996 / N0896.asc ). Но 12 лет спустя я не могу убедить ни gcc, ни g ++ дать мне какое-либо предупреждение за присвоение литерала неконстантному символу *, так что это не так уж и громко не рекомендуется.

[Редактировать: aha: -Wwrite-strings, которые не включены в -Wall или -Wextra]

0 голосов
/ 21 февраля 2009

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

0 голосов
/ 21 февраля 2009

Вкратце:

char *s = "HAPPY DAY";
printf("\n %s ", s);

s = "NEW YEAR"; /* Valid */
printf("\n %s ", s);

s[0] = 'c'; /* Invalid */
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...