将strtok标记分配给char * Segfault

3
为什么我使用下面的代码会出现段错误?
#include <stdio.h>

int main()
{
    char * tmp = "0.1";
    char * first = strtok(tmp, ".");
    return 0;
}

编辑:

#include <stdio.h>

int main()
{
    char tmp[] = "0.1";
    char *first = strtok(tmp, ".");
    char *second = strtok(tmp, "."); // Yes, should be NULL
    printf("%s\n", first);       
    printf("Hello World\n");
    return 0;
}

这个段错误可以在在线gdb中重现,网址如下: https://www.onlinegdb.com/online_c_compiler


char * tmp = "0.1"; 会让编译器将字符串写入可执行文件中,无法在运行时修改。而 char tmp[] = "0.1"; 则会将字符串推入堆栈中,可以在运行时进行修改。 - RayaneCTX
@user3121023 我明白了,谢谢!现在当我尝试打印令牌时又出现了段错误:printf("%s\n", first); 你知道为什么吗? - ajfbiw.s
你能否尝试使用链接中的在线gdb?我正在使用它,但它给了我一个sefgault错误。 - ajfbiw.s
即使更改为tmp [],仍然存在问题。第一个strtok将在原地修改tmp并将其留为空。因此,第二个strtok将获得0而不是您想要的1(因为它看到的是0而不是原始的0.1)。您需要执行以下操作:second = strtok(NULL,“。”)以获取1 - Craig Estey
@user3121023 你说得对,我应该使用NULL,但是我仍然遇到了段错误。 - ajfbiw.s
显示剩余2条评论
4个回答

4
你的第一段代码的问题在于tmp指向了一个只读字符串。当strtok试图修改该字符串时,程序会崩溃。
你的第二段代码的问题是缺少了一个包含文件:
#include <string.h>

这个缺少的头文件意味着你的程序中未声明strtok。C编译器假定所有未声明的函数返回int,但是strtok并不是如此,它返回char *。你的示例程序崩溃的可能原因是代码正在64位机器上运行,指针的宽度为8字节,而int只有4字节,这会导致strtok的返回值出错,所以first成为一个无用的指针(当printf试图使用它时,会导致程序崩溃)。

你可以通过执行以下操作来确定:

char *first = strtok(tmp, ".");
printf("%p %p\n", (void *)tmp, (void *)first);

tmpfirst的地址应该相同(如果你包含了#include <string.h>,它们是相同的)。


有趣的是,gcc可以警告你这些问题:

main.c: In function 'main':
main.c:6:19: warning: implicit declaration of function 'strtok' [-Wimplicit-function-declaration]
     char *first = strtok(tmp, ".");
                   ^
main.c:6:19: warning: initialization makes pointer from integer without a cast [-Wint-conversion]
main.c:7:20: warning: initialization makes pointer from integer without a cast [-Wint-conversion]
     char *second = strtok(tmp, "."); // Yes, should be NULL
                    ^

如果编译失败,onlinegdb将向您显示这些警告!

因此,要在onlinegdb上查看编译器警告,您必须向代码添加一个硬错误(例如,在文件的最后一行放置@)。


3
函数 strtok 的行为如下所述:
  1. 接受字符串 str 或 NULL,以及分隔符字符的字符串。
  2. 然后,strtok 函数开始处理给定的字符串 str,在其中逐个读取字符串字符,直到遇到提供的分隔符字符之一。
  3. 如果它在到达分隔符字符串之前遇到的字符数 > 0,则用 '\n' 替换分隔符字符,并返回此迭代中第一个不是分隔符字符的字符的指针。
  4. 否则,如果它在到达分隔符字符串之前遇到的字符数为 == 0,则继续迭代字符串的其余部分,而不替换此分隔符字符为 '\n'。
我创建了一些代码片段,可以帮助您更好地理解函数的性质,链接如下:https://ideone.com/6NCcrRhttps://ideone.com/KVI5n4

现在回答您的问题,包含 string.h 头文件并设置 char tmp[] = "0.1"; 应该解决您的问题。


那并没有真正回答问题。问题是“为什么这段代码会引起segfault?”,而不是“如何消除segfault?” - melpomene
是的,你说得对。只是想稍微介绍一下strtok函数的内部工作原理。在我写出解释的时候,这个问题已经被充分讨论过了,所以我决定简短说明。无论如何,我认为它仍然对任何来到这里的人有一点帮助,所以就让它保留吧 :) - Nilesh

2
使用 char * tmp = "0.1";tmp 指向一个不能被修改的字符串常量,而 strtok 尝试通过用 '\0' 替换 . 来修改该字符串。

另一种避免段错误的方法是使用 strchr 查找点和精度字段来打印有限数量的字符。子字符串也可以复制到其他变量中。

#include <stdio.h>
#include <string.h>

int main ( void) {
    char * tmp = "0.1";
    char * first = strchr(tmp, '.');
    char * second = first + 1;
    if ( first) {
        printf ( "%.*s\n", first - tmp, tmp);
        printf ( "%s\n", second);
    }
    printf ( "Hello World\n");
    return 0;
}

很酷,但我不明白它如何回答问题。 - melpomene

0

tmp 不是字符串文字,正如一些回答或评论所指出的。

char *tmp = "0.1" 这是一个字符串文字。

char tmp[] = "0.1" 是一个字符数组,可以对它们执行所有的数组操作。

由于没有找到 strtok 的函数声明,因为没有包含 string.h,gcc 或其他 C 编译器默认将返回类型隐式声明为 int,所以导致段错误。

现在根据平台的不同,整数大小可能会有所变化,如果 int 大小分别为 4 字节和指针大小为 8 字节

char *first = (int)strtok(tmp,".");

截断发生在 strtok 返回的指针地址上,然后当您打印时,您对 first 中包含的地址值进行了解引用,这可能是一个超出界限的内存区域,导致分段错误或未定义行为。

如果你可以将strtok的输出强制转换为一个8字节的类型(在我的情况下是long),那么就不会出现段错误,尽管这不是一种干净的方法。

包含适当的头文件以避免未定义的行为。


你的两个例子都不是字符串字面量。"0.1"才是字符串字面量;你的代码展示了一个变量定义(带有初始化器)。第一个声明将tmp声明为指针(并用字符串字面量进行初始化),第二个声明将tmp声明为数组(并用字符串字面量进行初始化)。如果函数声明的返回类型错误,强制转换结果为另一种类型也无法解决问题。 - melpomene
第一个例子 char *tmp = "0.1",使 tmp 指向只读内存,其中存储了 "0.1"。在第二个例子中,char tmp[] = "0.1" 为字符串分配内存并将字符串复制到其中,因此修改它是有效的。而字符串字面量被分配在只读内存中。 - Suresh Krishna
正如你所说,“0.1”是一个字符串字面量,根据定义是由双引号包围的字符序列。但修改tmp是有效的,因为它是一个字符数组。我确实说过这不是一种干净的方式将其转换为不同类型,希望你能理解我的观点。这只是建议性的,以便了解为什么会出现段错误。 - Suresh Krishna

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