Ошибка сегментации strtok

Я пытаюсь понять, почему следующий фрагмент кода дает ошибку сегментации:

void tokenize(char* line)
{
   char* cmd = strtok(line," ");

   while (cmd != NULL)
   {
        printf ("%s\n",cmd);
        cmd = strtok(NULL, " ");
   } 
}

int main(void)
{
   tokenize("this is a test");
}

Я знаю, что strtok() фактически не tokenize в строковых литералах, но в этом случае line указывает непосредственно на строку "this is a test", которая внутренне является массивом char. Есть ли какой-либо токенизирующий line без копирования его в массив?

Ответ 1

Проблема заключается в том, что вы пытаетесь изменить строковый литерал. Это приводит к тому, что поведение вашей программы будет undefined.

Говорить, что вам не разрешено изменять строковый литерал, это упрощение. Утверждение, что строковые литералы const неверны; это не так.

ПРЕДУПРЕЖДЕНИЕ: Далее следует отступление.

Строковый литерал "this is a test" имеет выражение типа char[15] (14 для длины, плюс 1 для завершающего '\0'). В большинстве контекстов, включая это, такое выражение неявно преобразуется в указатель на первый элемент массива типа char*.

Поведение попытки изменить массив, на которое ссылается строковый литерал, - это undefined - не потому, что оно const (это не так), а потому, что в стандарте C конкретно говорится, что он undefined.

Некоторые компиляторы могут позволить вам избежать этого. Ваш код может фактически изменить статический массив, соответствующий литералу (что может вызвать большую путаницу позже).

Большинство современных компиляторов, тем не менее, будут хранить массив в постоянной памяти, а не в физическом ПЗУ, но в области памяти, которая защищена от модификации системой виртуальной памяти. Результатом попытки изменить такую ​​память обычно является ошибка сегментации и сбой программы.

Так почему же не строковые литералы const? Поскольку вы действительно не должны пытаться их модифицировать, это, безусловно, имеет смысл - и С++ делает строковые литералы const. Причина - историческая. Ключевое слово const не существовало до того, как оно было введено стандартом ANSI C 1989 года (хотя, вероятно, это было реализовано некоторыми компиляторами до этого). Таким образом, программа pre-ANSI может выглядеть так:

#include <stdio.h>

print_string(s)
char *s;
{
    printf("%s\n", s);
}

main()
{
    print_string("Hello, world");
}

Не удалось установить тот факт, что print_string не разрешено изменять строку, на которую указывает s. Создание строковых литералов const в ANSI C нарушило бы существующий код, который комитет ANSI C очень старался избегать. С тех пор не было хорошей возможности внести такое изменение в язык. (Дизайнеры С++, в основном Bjarne Stroustrup, не были так же обеспокоены обратной совместимостью с C.)

Ответ 2

Как вы сказали, вы не можете изменить строковый литерал, что и делает strtok. Вы должны сделать

char str[] = "this is a test";
tokenize(str);

Это создает массив str и инициализирует его с помощью this is a test\0 и передает указатель на него tokenize.

Ответ 3

Существует очень веская причина, по которой попытка токенизировать строку константы времени компиляции вызовет ошибку сегментации: постоянная строка находится в постоянной памяти.

Компилятор C выпекает постоянные строки времени компиляции в исполняемый файл, а операционная система загружает их в постоянную память (.rodata в файле * nix ELF). Поскольку эта память помечена как доступная только для чтения, и поскольку strtok записывает в строку, которую вы передаете в нее, вы получаете ошибку сегментации для записи в постоянную память.

Ответ 4

Strok изменяет свой первый аргумент, чтобы его токенизировать. Следовательно, вы не можете передать ему буквенную строку, такую ​​как тип const char * и не могут быть изменены, следовательно, поведение undefined. Вы должны скопировать строковый литерал в массив char, который можно изменить.

Ответ 5

Какой момент вы пытаетесь сделать своим "... внутренним массивом char"?

Тот факт, что "this is a test" является внутренним массивом char, ничего не меняет. Он по-прежнему является строковым литералом (все строковые литералы являются немодифицируемыми массивами char). Ваш strtok все еще пытается выполнить токенизацию строкового литерала. Вот почему он падает.

Ответ 6

Я уверен, что вы будете избиты об этом... но "strtok()" по своей сути является небезопасным и подверженным таким вещам, как нарушения доступа.

Здесь ответ почти наверняка использует строчную константу.

Попробуйте это вместо:

void tokenize(char* line)
{
   char* cmd = strtok(line," ");

   while (cmd != NULL)
   {
        printf ("%s\n",cmd);
        cmd = strtok(NULL, " ");
   } 
}

int main(void)
{
  char buff[80];
  strcpy (buff, "this is a test");
  tokenize(buff);
}

Ответ 7

Я просто ударил ошибку сегментации из попытки использовать printf для печати токена (cmd в вашем случае) после того, как он стал NULL.