strtok分割故障

19

我正在尝试理解为什么下面这段代码会导致段错误:

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 进行分词?

5
“Dude - 'this is a test'是一个字符串字面值,意思是它是一个只读的“字符数组”。你甚至可能在某些平台上尝试修改它而不会崩溃。但在任何平台上都绝对不可行。” - paulsm4
顺便说一下,这个错误并不总是SEGFAULT - 在我的情况下,它以"进程]被信号SIGBUS(地址对齐错误)终止"的形式出现。 - B00TK1D
8个回答

33

问题在于您试图修改一个字符串常量。这样做会导致程序的行为未定义。

说您不能修改字符串常量是一种过度简化。说字符串常量是const是不正确的;它们不是。

警告:以下是离题内容。

字符串字面值"this is a test"是类型为char[15](14个字符加1个终止符'\0')的表达式。在大多数情况下,包括此处,这样的表达式会被隐式转换为指向数组第一个元素的指针,类型为char*

尝试修改由字符串字面值引用的数组的行为是未定义的--不是因为它是const(它不是),而是因为C标准明确规定了它是未定义的。

某些编译器可能允许您这样做。您的代码实际上可能会修改与字面量对应的静态数组(这可能会在以后导致混乱)。

但是,大多数现代编译器将存储该数组在只读内存中--不是物理ROM,而是在由虚拟内存系统保护免受修改的内存区域中。 尝试修改这样的内存的结果通常是分段错误和程序崩溃。

那么为什么字符串字面值不是const?既然您确实不应该尝试修改它们,那肯定有道理--C ++确实让字符串字面值成为const。原因是历史原因。在1989年ANSI C标准引入之前,const关键字并不存在(尽管某些编译器可能在此之前就已经实现了它)。因此,一个ANSI之前的程序可能如下所示:

#include <stdio.h>

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

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

无法强制规定print_string不允许修改指向s的字符串。将ANSI C中的字符串文字声明为const将破坏现有代码,这是ANSI C委员会非常努力避免的。此后没有好的机会来进行这种语言变更。(C++的设计者,主要是Bjarne Stroustrup,并不像对于与C的向后兼容性一样关注。)


@KoushikShomChoudhury:或者有人在我的答案中看到了问题,决定对它进行了负评,并且很可能没有看到我三年之间发表的评论。也许他们认为我的离题太多了,这将是一个有效的批评。投票是匿名的设计。曾考虑过要求或至少鼓励在负面评价时附加评论的想法并被拒绝。个人认为鼓励评论是一个好主意,也许有一种方法可以让评论通知负评者(尽管这可能很难实现)。 - Keith Thompson

5

尝试对编译时常量字符串进行分词会导致分段错误的一个很好的原因是:常量字符串在只读内存中。

C编译器将编译时常量字符串嵌入可执行文件中,操作系统将它们加载到只读内存中(在*nix ELF文件中为.rodata)。由于此内存被标记为只读,而strtok要写入传递给它的字符串,因此在写入只读内存时会导致分段错误。


5
正如你所说,你无法修改字符串常量,而这正是strtok所做的。你需要进行如下操作:
char str[] = "this is a test";
tokenize(str);

这将创建数组str并用this is a test\0初始化它,然后将指向它的指针传递给tokenize函数。

4

Strok修改它的第一个参数以对其进行标记。因此,您不能将字面字符串传递给它,因为它是const char *类型,不能被修改,因此会导致未定义的行为。您必须将字符串字面量复制到可以修改的char数组中。


2

我也遇到了这个错误,但我找到了一个简单的解决方案。

请包含 <string.h>,这样就可以消除 strtok 分段错误。


在变量字符数组(不是字符串字面值)的情况下,同样的错误,同样的解决方法:包含 <string.h>。 - alboforlizo

2
你的 "…是内部 char 数组" 的评论想要表达什么意思?
事实上,"this is a test" 内部是一个 char 数组并不会改变任何东西。它仍然是一个字符串字面量(所有字符串字面量都是不可修改的 char 数组)。你的 strtok 仍然尝试对一个字符串字面量进行分词。这就是为什么它会崩溃的原因。

1

我相信你会因为这个而受到指责...但是 "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调用变成缓冲区溢出漏洞。 - Adam Mihalcin

0

我刚刚因为尝试在变量(在你的情况下是cmd)变成NULL后使用printf打印令牌而遇到了分段错误。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接