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

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 [] переменная массива находится в памяти для чтения-записи, а код инициализатора при запуске C копирует строковый литерал в массив.

John Safranek 07.11.2008 21:08
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
5
1
6 186
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Ответ принят как подходящий

Чем вы инициализировали 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"; Версия?

Gilbert 07.11.2008 21:01

Я только что попробовал. Ничто не мешает вам сделать это, кроме segfault, потому что "text + 3" по-прежнему относится к постоянной памяти.

John Safranek 07.11.2008 21:07

@Paul: strsep - плохая замена strtok, он страдает от многих из тех же проблем, что и strtok, а именно, если изменяет строку и не работает со строковыми литералами.

Robert Gamble 07.11.2008 21:17

Роберт, да, стрцеп тоже бедный. Предлагаемая альтернатива?

The Archetypal Paul 07.11.2008 21:26

@Paul: простое решение - сначала сделать копию строки, чтобы убедиться, что у вас есть модифицируемая версия строки и работать с ней.

Evan Teran 07.11.2008 21:34

@Evan, ну да :) Я действительно интересовался, есть ли какие-нибудь стандартные библиотечные функции, чтобы помочь OP

The Archetypal Paul 08.11.2008 10:59

@JJohn и @Gilbert: текст в памяти указывает на нет только для чтения. Это чушь. Вы неправильно понимаете арифметику указателей. text + 3 - это не третий символ в строке, а указатель далеко за концом (фактически 3 * sizeof (char *) байтов). Вам нужен текст [3]

Bklyn 30.07.2010 09:01

Виноват стандарт 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. Даже попытки изменить его сегодня столкнулись бы с аналогичным противодействием. (как кажется, вы находите).

Michael Burr 07.11.2008 22:53

Это объясняет исторические причины, спасибо. Я предполагаю, что моя жалоба заключается в том, что современные компиляторы C и C++ должны предупреждать, возможно, изначально на очень высоком уровне предупреждения. Я не возражаю против нескольких предупреждений из кода эры K&R, но было бы неплохо, если бы новый код не делал этого.

Steve Jessop 08.11.2008 14:47

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

Основываясь на вашем вопросе, я предполагаю, что вы новичок в программировании на 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 (или «нулевой символ»).

Chris Lutz 21.02.2009 05:03

Вкратце:

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

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

s[0] = 'c'; /* Invalid */

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

Другие вопросы по теме