2012-01-22 3 views
10

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

+2

Чувак - «это тест» - это STRING LITERAL. Это означает, что это * ЧИТАТЬ ТОЛЬКО * «массив символов». Вы даже можете уйти, пытаясь изменить его без сбоев на определенных платформах. Но это определенно нет-нет на * ЛЮБОЙ * платформе :) – paulsm4

ответ

14

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

Сказать, что вам не разрешено изменять строковый литерал, является упрощением. Утверждение, что строковые литералы 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++, в основном Bjarne Stroustrup, не были так обеспокоены обратной совместимостью с C.)

+0

отличное объяснение !!! – ademar111190

+1

Помогло ли бы downvoter объяснить? –

2

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

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

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

0

Я уверен, что вы будете избиты об этом ... но «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); 
} 
+1

Если вы собираетесь выявить небезопасный характер strtok, мы могли бы также помнить, что strncpy намного безопаснее, чем strcpy. Хотя strcpy совершенно безопасен для строки константы времени компиляции, более поздний рефакторинг может превратить вызов strcpy в уязвимость переполнения буфера. –

1

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

2

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

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

1

Что вы пытаетесь сделать своим «... внутренне массив char» примечание?

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

0

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

Смежные вопросы