ошибка сегментации strtok - PullRequest
12 голосов
/ 22 января 2012

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

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 () на самом деле не использует токены для строковых литералов, но в этом случае line указывает непосредственно на строку "this is a test", которая является внутренним массивом char.Есть ли токенизация line без копирования в массив?

Ответы [ 7 ]

20 голосов
/ 22 января 2012

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

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

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

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

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

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

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

Так почему не являются строковыми литералами const?Поскольку вам действительно не следует пытаться изменять их, это, безусловно, имеет смысл - и C ++ делает строковые литералы const.Причина историческая.Ключевое слово const не существовало до того, как оно было введено стандартом ANSI C 1989 года (хотя, возможно, до этого оно использовалось некоторыми компиляторами).Таким образом, предварительная 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 очень старался избежать.С тех пор не было хорошей возможности внести такие изменения в язык.(Разработчики C ++, в основном Бьярне Страуструп, не были так обеспокоены обратной совместимостью с C.)

2 голосов
/ 22 января 2012

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

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

2 голосов
/ 22 января 2012

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

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

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

1 голос
/ 22 января 2012

Какую точку зрения вы пытаетесь выразить в "... внутренне массиве char" примечания?

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

1 голос
/ 22 января 2012

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

1 голос
/ 22 января 2012

Я уверен, что вы будете избиты по этому поводу ... но 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);
}
0 голосов
/ 27 июня 2016

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

...