【问题标题】:Why do I get a segmentation fault when using strncpy?为什么在使用 strncpy 时会出现分段错误?
【发布时间】:2010-04-07 15:40:02
【问题描述】:

使用 strncpy 和 (pointer-to-struct)->(member) 表示法时出现分段错误:

我已经简化了我的代码。我初始化一个结构并将它的所有标记设置为一个空字符串。然后声明一个指向结构的指针并将结构的地址分配给它。

我将指针传递给一个函数。我可以在函数的开头打印出结构的内容,但是如果我尝试在 strncpy 函数中使用 tp -> mnemonic,我会遇到段错误。谁能告诉我我做错了什么?

typedef struct tok  {
    char* label;
    char* mnem;
    char* operand;
}Tokens;

Tokens* tokenise(Tokens* tp, char* line)  {
    // This prints "load"
    printf("Print this - %s\n", tp -> mnem);

    // This function gives me segmentation fault
    strncpy(tp -> mnem, line, 4);

    return tp;
}

int main()  {
    char* line = "This is a line";
    Tokens tokens;
    tokens.label = "";
    tokens.mnem = "load";
    tokens.operand = "";

    Tokens* tp = &tokens;
    tp = tokenise(tp, line);

    return 0;
}

我已使用 printf 语句确认代码在 strncpy 函数处确实停止执行。

【问题讨论】:

    标签: c


    【解决方案1】:

    问题在于 tp->mnem 指向的是一个字符串字面量,这个字面量一般分配在一个只读的内存段中。因此,覆盖它是非法的。您最有可能需要做的是这样的事情:

    Tokens tokens;
    tokens.label = "";
    tokens.mnem  = strdup("load");
    tokens.operand = "";
    

    这将为您提供一个动态分配的内存块用于 mnem,然后您可以随意写入。当然,您还有其他一些问题:首先,您需要记住稍后使用free 释放该内存;其次,你必须知道你分配的缓冲区的大小,以免覆盖它。

    如果您知道mnem 的内容永远不会超过 4 个字节,那么您可以像这样更改您的结构声明:

    typedef struct tok  {
        char* label;
        char mnem[5]; // note: +1 byte for a NULL terminator
        char* operand;
    }Tokens;
    

    然后,你可以这样初始化它:

    Tokens tokens;
    tokens.label = "";
    strcpy(tokens.mnem, "load");
    tokens.operand = "";
    

    这免除了您为mnem 管理内存的责任,尽管您仍有一些溢出缓冲区的风险。

    【讨论】:

    • 如果您要将 mnem 视为一个字符串,您应该将其延长一个字符并确保它以空值结尾。否则,您可能会在 sprintf 中找到附加的其他字段和粘液。
    • @Ian G:好点,我更新了我的答案以反映这个建议。
    【解决方案2】:

    下一行

    tokens.mnem = "load"
    

    mnem 分配给字符串文字的地址,该地址通常位于只读数据段中,因此使用strncpy() 或任何其他函数更改此内存将失败。

    【讨论】:

      【解决方案3】:

      问题是您已将字符串文字分配给Tokens 结构的成员,并试图覆盖tokenise 中的内存(特别是mnem 字段)。

      大多数现代操作系统都会从程序地址空间的特殊只读部分为字符串文字分配内存。如果您尝试写入该内存,那么您的程序将因段错误而死。

      这就是为什么字符串文字的类型是const char *,而不是char *。当您尝试将这些分配给 tokenise 的字段时,您的编译器应该会发出警告。

      如果以后要覆盖内存,则需要使用malloc动态分配内存或将Tokens结构的成员更改为定长数组,然后将初始值复制到分配的内存中。当然,如果你动态分配内存,你也需要稍后free

      【讨论】:

        【解决方案4】:

        正如Shadow 所说,您在没有分配缓冲区空间的情况下调用strncpy()

        您在初始化程序中将mnem 成员设置为的文字字符串"load" 不可覆盖。

        如果您希望能够更改存储的字符串,并且大小合理,则将结构字段的声明更改为char mnem[5]; 可能是最简单的。

        另外,请注意strncpy() 具有非常奇怪的语义。检查您是否有@987654321@;这是一个更好的功能。

        【讨论】:

          【解决方案5】:

          您遇到了分段错误,因为这行:

          strncpy(tp -> mnem, line, 4);
          

          正在尝试将 'line' 中的四个字符复制到此处分配的字符串文字占用的位置:

          tokens.mnem = "load";
          

          字符串文字存储在程序的特殊文本部分中,不得修改。

          你需要做的是分配一个你自己的缓冲区来复制字符串:

          tokens.mnem = (char*) malloc (bufferSize);
          

          并在使用完缓冲区后释放它。

          【讨论】:

            【解决方案6】:

            这行有问题:

            strncpy(tp -> mnem, line, 4);
            

            您依赖的函数返回指向未分配内存的指针。 *tokenise() 的返回未定义。它返回一个指向内存的指针,该指针可能包含各种内容,并且您无权修改。

            应该返回一个分配的指针。

            【讨论】:

              【解决方案7】:

              您可以 malloc tp 变量。如果您不使用 malloc,则无法保证内存实际上是您的。完成后不要忘记释放内存。

              【讨论】:

              • 我不明白我为什么要 malloc tp 变量。那是一个指针,它指向一些已经分配给令牌结构的内存。如果我 malloc,那将分配一些不是令牌结构的新内存,不是吗?我不想那样。
              • 您对示例代码中“tp”引用的内存的所有权非常错误。它是在堆栈上声明的结构的地址。如图所示,使用指向它的指针是完全合法的,直到被引用的结构超出范围为止。那时内存被释放,引用它不再安全。 OP 代码中的错误在其他地方(请参阅我的回复)。
              • 我只是根据我为 457 网络课程编写的 3 个程序发表评论。对不起,我错了。
              • Shadow:很高兴你能帮忙。如果可以的话,让我为您未来的编程工作提供一些建议:提防“货物崇拜”编程,您以某种方式编写代码只是因为“这就是它一直是这样做的”,而是尝试理解为什么它可能已经这样做了,以便您可以决定在新情况下是否真的有必要这样做。祝你好运。
              • 我只是没仔细看 sn-p,我很抱歉。(rampion 感谢您对它的恶作剧)我不想尝试“货物崇拜”计划。我只是说我的错误发生了,因为我在戳我不应该出现的内存,一个简单的 malloc 解决了这个问题。并感谢 Eric 指出我的问题。我只是希望我在这里的第一个答案不会受到如此轻视。
              猜你喜欢
              • 2015-01-27
              • 2011-12-23
              • 2011-04-23
              相关资源
              最近更新 更多