【问题标题】:Weird Segmentation Fault when doing strcpy the 2nd time on the same string C在同一字符串 C 上第二次执行 strcpy 时出现奇怪的分段错误
【发布时间】:2018-05-12 15:15:24
【问题描述】:

我正在标记由逗号分隔的行,并且由于某些标题中包含逗号,因此它们被引号包围。我标记化到第一个引号,将其存储在 fronttoken 中,然后到第二个引号,将其存储到标题中,最后我标记化直到 \n。我不明白为什么。

它与strsep有关吗?我没有使用 strtok,因为如果字符串由“,”组成,多个分隔符之间没有任何内容,我想捕获空标记。

由于 tempStr 的分配远远超过了必要的数量,它应该足以作为 strcpy 的 *dest。我已经坚持了几个小时。如果有人能指出我的错误,我将不胜感激。谢谢。

int main(int argc, char * argv[])
{   
    char* one = "hello, my, name, is, code monkey, \"This, is a title\", more, random, stuff\n";
    char* two = "blah blah blah";

    char* tempStr= malloc(1000);
    void* freeTempStr = tempStr;

    strcpy(tempStr, one);

    char* fronttoken = strsep(&tempStr, "\"");
    char* title = strsep(&tempStr, "\"");
    char* backtoken = strsep(&tempStr, "\n");
    char* token;

    strcpy(tempStr, fronttoken);
    token = strsep(&tempStr, ",");
    while (token != NULL)
    {
        printf("Front tokens: %s\n", token);
        token = strsep(&tempStr, ",");
    }
    printf("Title: %s\n", title);
    strcpy(tempStr, backtoken);
    token = strsep(&tempStr, ",");
    while (token != NULL)
    {
        printf("Back tokens: %s\n", token);
        token = strsep(&tempStr, ",");
    }
    //2nd strcpy gives segmentation fault

    free(freeTempStr)
    return 0;
}

输出...

Front tokens: hello
Front tokens:  my
Front tokens:  name
Front tokens:  is
Front tokens:  code monkey
Front tokens:
Title: This, is a title
Segmentation fault

【问题讨论】:

  • 我不明白你在做什么,你真的了解strsep()的行为吗?
  • 如果找不到分隔符,strsep 会将tempStr 设置为NULL,因此您可能将NULL 作为第一个参数传递给strcpy,从而导致段错误。您可以通过使用调试器并在发生段错误时检查 tempStr 的值来测试此假设
  • @Stargateur 我正在尝试正确标记包含电影属性的大型 CSV 文件。有些电影的标题中有逗号,这就是为什么我有一个前后标记以便绕过标题中的逗号。
  • 格式正确的 CSV 文件将在包含逗号的字段周围使用双引号。如果字段以双引号开头,则必须进行不同的解析。如果您的文件格式不正确,那么它就是模棱两可的。此外,如果处理不当,您将丢失字符串中嵌入的逗号。在双引号字段中,输入中的两个相邻双引号映射到转换后的字符串中的一个。当然,您必须在输出时撤消此操作。
  • @M.M 在对前面的token进行token化之前tempStr的初始值为0x604440 "" 那么当strsep对最后一个token进行token化时,tempStr就变成了0x0。在再次使用之前,我是否必须再次 malloc tempStr?

标签: c strcpy fault


【解决方案1】:

仔细查看代码并对其进行编译和试验,我同意M.Manalysis 的直接问题是你 试图复制到一个空指针。

但是,我怀疑您总体上没有仔细考虑内存管理。当您执行strcpy(tempStr, one); 时,您将源字符串复制到分配的内存中。 (不)幸运的是,您分配的内存比所需的副本多得多。当您随后执行strcpy(tempStr, fronttoken); 时,会将fronttoken 复制到tempStrone 的原始副本之后的位置。然后你把它分开。

tempStr 被设置为空时你停止了崩溃,然后你尝试strcpy(tempStr, backtoken),复制到空指针。

如果你解决了这个问题,你可能会遇到重叠字符串复制的问题。您当前的后备标记集足够小,这不是问题,但如果您有 100 字节的后退标记,则会有重叠的字符串副本和未定义的行为。

此代码显示问题并修复它。请注意,它在标记周围包含方括号,因此更容易准确查看找到的内容。

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

int main(void)
{   
    char* one = "hello, my, name, is, code monkey, \"This, is a title\", more, random, stuff\n";

    char* tempStr= malloc(1000);
    void* freeTempStr = tempStr;

    strcpy(tempStr, one);
    printf("tempStr = [%p .. %p)\n", (void *)tempStr, (void *)(tempStr + 1000));

    char* fronttoken = strsep(&tempStr, "\"");
    printf("tempStr = %p; fronttoken = %p\n", (void *)tempStr, (void *)fronttoken);
    char* title = strsep(&tempStr, "\"");
    printf("tempStr = %p; title = %p\n", (void *)tempStr, (void *)title);
    char* backtoken = strsep(&tempStr, "\n");
    printf("tempStr = %p; backtoken = %p\n", (void *)tempStr, (void *)backtoken);
    char* token;

    printf("tempStr = %p; fronttoken = %p - before strcpy 1\n", (void *)tempStr, (void *)fronttoken);
    strcpy(tempStr, fronttoken);
    token = strsep(&tempStr, ",");
    while (token != NULL)
    {
        printf("Front tokens: %p [%s]\n", (void *)token, token);
        token = strsep(&tempStr, ",");
    }
    printf("Title: [%s]\n", title);
    printf("tempStr = %p; backtoken = %p - before strcpy 2 (unfixed)\n", tempStr, backtoken);
    tempStr = freeTempStr;
    printf("tempStr = %p; backtoken = %p - before strcpy 2 (fixed - but beware overlap)\n", tempStr, backtoken);
    strcpy(tempStr, backtoken);
    token = strsep(&tempStr, ",");
    while (token != NULL)
    {
        printf("Back tokens: %p [%s]\n", (void *)token, token);
        token = strsep(&tempStr, ",");
    }

    free(freeTempStr);
    return 0;
}

示例输出(运行 macOS High Sierra 10.13.1 的 Mac,安装了安全更新 2017-001 — macOS 10.13.1 (17B1002) — 如果您还没有这样做,请安装它!):

tempStr = [0x7fb91ac02880 .. 0x7fb91ac02c68)
tempStr = 0x7fb91ac028a3; fronttoken = 0x7fb91ac02880
tempStr = 0x7fb91ac028b4; title = 0x7fb91ac028a3
tempStr = 0x7fb91ac028ca; backtoken = 0x7fb91ac028b4
tempStr = 0x7fb91ac028ca; fronttoken = 0x7fb91ac02880 - before strcpy 1
Front tokens: 0x7fb91ac028ca [hello]
Front tokens: 0x7fb91ac028d0 [ my]
Front tokens: 0x7fb91ac028d4 [ name]
Front tokens: 0x7fb91ac028da [ is]
Front tokens: 0x7fb91ac028de [ code monkey]
Front tokens: 0x7fb91ac028eb [ ]
Title: [This, is a title]
tempStr = 0x0; backtoken = 0x7fb91ac028b4 - before strcpy 2 (unfixed)
tempStr = 0x7fb91ac02880; backtoken = 0x7fb91ac028b4 - before strcpy 2 (fixed - but beware overlap)
Back tokens: 0x7fb91ac02880 []
Back tokens: 0x7fb91ac02881 [ more]
Back tokens: 0x7fb91ac02887 [ random]
Back tokens: 0x7fb91ac0288f [ stuff]

请注意,使用调试器还可以让您很快找到此问题。

【讨论】:

    【解决方案2】:

    您的代码没有按照发布的那样编译;缺少分号。此外,还缺少许多标准标头。

    在带有 gcc 6.2.6 的 64 位 Ubuntu 16 上,崩溃发生在您观察到的位置之外。因为缺少&lt;string.h&gt; 标头,所以strsep 函数被隐式声明为返回int,这是错误的。因此,fronttoken 变量接收到一个垃圾值,第一个 strcpy 失败。

    首先要做的是获得一个没有错误或警告的干净构建(在打开编译器用户社区广泛推荐的任何诊断之后)。

    在所有这些都解决之后,你有一个简单的逻辑问题:

    while (token is not null) {
       token = strsep(&tempStr, ...)
    
    }
    
    strcpy into tempStr
    

    由于while 循环不包含break 语句,它终止的唯一方法是token 变为空。

    token 变为 null 的唯一方法是 strsep 返回 null。

    根据文档,strsep 返回 null 的唯一方法是 tempStr 为 null。

    token 为空的事实证明tempStr 必须为空,这意味着我们不能使用tempStr 作为strcpy 的目的地。

    tempStr 变为 null 的原因在于,在之前对 strsep 的调用中,没有找到标记分隔符。在这种情况下,strsep 将整个字符串作为提取的标记返回,并用 null 覆盖指针。

    换句话说,在从字符串中提取最后一个标记后,strsep 用 null 覆盖指针。然后下一次调用strsep,它返回null,表示“没有更多的令牌可用”。这使得strsep 易于使用:只要继续调用它,直到你得到一个空值。但是您必须了解临时上下文指针在此过程中会被清空。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-03
      • 2016-05-14
      • 2012-05-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多