【问题标题】:Confusion in "strcat function in C assumes the destination string is large enough to hold contents of source string and its own."“C 中的 strcat 函数假定目标字符串足够大以容纳源字符串及其自身的内容”中的混淆。
【发布时间】:2019-01-22 10:32:57
【问题描述】:

所以我读到strcat 函数要小心使用,因为目标字符串应该足够大以容纳它自己和源字符串的内容。我编写的以下程序也是如此:

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

int main(){
    char *src, *dest;
    printf("Enter Source String : ");
    fgets(src, 10, stdin);
    printf("Enter destination String : ");
    fgets(dest, 20, stdin);
    strcat(dest, src);
    printf("Concatenated string is %s", dest);
    return 0;
}

但我在这里写的不是这样:

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

int main(){
    char src[11] = "Hello ABC";
    char dest[15] = "Hello DEFGIJK";
    strcat(dest, src);
    printf("concatenated string %s", dest);
    getchar();
    return 0;
}

这个程序最终将两者相加,而没有考虑到目标字符串不够大。为什么会这样?

【问题讨论】:

  • 未定义的行为包括“似乎有效”。
  • 您可能预计会出现某种错误。虽然没有报告错误,但您的代码仍然是错误的:它没有定义您的代码在执行时将做什么。这可能是一个微妙的点,但却是一个重要的点。例如:stackoverflow.com/questions/2397984/… 或 en.wikipedia.org/wiki/Undefined_behavior
  • 欢迎来到本站!查看how-to-ask page 了解更多关于提出问题以吸引高质量答案的信息。如果评论者要求,您可以edit your question 提供更多信息。感谢您包含您尝试过的代码 --- 您从这两个程序中得到了什么输出?
  • 您的第一个程序比您的第二个程序更糟糕。指针src 和dest 指向哪里??
  • 你认为输入是fgets(src, 10, stdin);在哪里写的;特别是在第一个代码示例的情况下?这是讨论 strcat 操作的重要基础。

标签: c string strcat


【解决方案1】:

strcat 函数无法确切知道目标缓冲区的长度,因此它假定传递给它的缓冲区足够大。如果不是,则通过写入缓冲区末尾来调用undefined behavior。这就是第二段代码中发生的事情。

第一段代码也是无效的,因为src 和dest 都是未初始化的指针。当您将它们传递给fgets 时,它会读取它们包含的任何垃圾值,将其视为有效地址,然后尝试将值写入该无效地址。这也是未定义的行为。

使 C 快速的原因之一是它不会检查以确保您遵守规则。它只是告诉你规则并假设你遵守它们,如果你不这样做,坏事可能会发生也可能不会发生。在您的特定情况下,它似乎有效,但不能保证。

例如,当我运行您的第二段代码时,它似乎也可以工作。但是如果我把它改成这样:

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

int main(){
    char dest[15] = "Hello DEFGIJK";
    strcat(dest, "Hello ABC XXXXXXXXXX");
    printf("concatenated string %s", dest);
    return 0;
}

程序崩溃。

【讨论】:

    【解决方案2】:

    为了说明问题,我想指出第二个程序中实际发生的情况。

    它在从 dest 开始的内存位置分配 15 个字节,并将 14 个字节复制到其中(包括空终止符):

        char dest[15] = "Hello DEFGIJK";
    

    ...和 ​​src 中的 11 个字节,其中 10 个字节被复制到其中:

        char src[11] = "Hello ABC";
    

    然后,strcat() 调用将 10 个字节(9 个字符加上空终止符)从 src 复制到 dest,从 dest 中的“K”之后开始。 dest 处的结果字符串长度为 23 个字节,包括空终止符。问题是,您在 dest 只分配了 15 个字节,与该内存相邻的内存将被覆盖,即损坏,导致程序不稳定、结果错误、数据损坏等。

    请注意,strcat() 函数对您在 dest(或 src,就此而言)分配的内存量一无所知。您需要确保已在 dest 分配了足够的内存以防止内存损坏。

    顺便说一句,第一个程序根本不会在 dest 或 src 分配内存,因此您对 fgets() 的调用会破坏从这些位置开始的内存。

    【讨论】:

      【解决方案3】:

      两个 sn-ps 都调用 未定义的行为 - 第一个是因为 src 和 dest 没有初始化为指向任何有意义的地方,第二个是因为您正在写超出数组的末尾。

      C 不强制对数组访问进行任何类型的边界检查 - 如果您尝试写入超出数组末尾的内容,则不会出现“索引超出范围”异常。如果您尝试越过页面边界访问或破坏诸如帧指针之类的重要内容,您可能会遇到运行时错误,否则您只会冒损坏程序中数据的风险。

      是的,您有责任确保目标缓冲区足够大以容纳最终字符串。否则结果是不可预测的。

      【讨论】:

        【解决方案4】:

        我认为您的困惑实际上与strcat 的定义无关。您真正的困惑是您假设 C 编译器将强制执行所有“规则”。这个假设是完全错误的。

        是的,strcat 的第一个参数必须是指向足以存储连接结果的内存的指针。在您的两个程序中,都违反了该要求。由于两个程序中都没有错误消息,您可能会得到这样的印象,即规则可能不是您认为的那样,即使第一个参数不是足够的指针,调用strcat 也是有效的记忆。但是不,事实并非如此:当内存不足时调用strcat 肯定是错误的。没有错误消息,或者一个或两个程序似乎“工作”的事实证明什么都没有。

        这是一个类比。 (你小时候甚至可能有过这种经历。)假设你妈妈告诉你不要跑过马路,因为你可能会被车撞到。假设你无论如何都跑过马路,并且没有被车撞到。你认为你母亲的建议是错误的吗?这是一个有效的结论吗?

        总而言之,您阅读的内容是正确的:strcat 必须谨慎使用。但是让我们重新表述一下:你在调用strcat 时必须小心。如果你不小心,各种各样的事情都会出错,没有任何警告。事实上,许多风格指南建议根本不要使用诸如strcat 之类的函数,因为如果你不小心,它们很容易被误用。 (只要你小心,strcat 等函数可以完全安全地使用——但当然不是所有的程序员都足够小心。)

        【讨论】:

        • 非常好的类比
        【解决方案5】:

        strcat() function 确实要小心使用,因为它不能保护您免受任何伤害。如果源字符串不是以 NULL 结尾的,目标字符串不是以 NULL 结尾的,或者目标字符串没有足够的空间,strcat 仍然会复制数据。因此,很容易覆盖您不想覆盖的数据。确保您有足够的空间是您的责任。使用strncat() 而不是strcat 也会给您一些额外的安全性。

        编辑这是一个例子:

        #include <stdio.h>
        #include <string.h>
        
        int main()
        {
            char s1[16] = {0};
            char s2[16] = {0};
            strcpy(s2, "0123456789abcdefOOPS WAY TOO LONG");
              /* ^^^ purposefully copy too much data into s2 */
            printf("-%s-\n",s1);
            return 0;
        }
        

        我从未分配给s1,所以理想的输出应该是--。但是,由于编译器碰巧在内存中排列s1 和s2,我实际得到的输出是-OOPS WAY TOO LONG-。 strcpy(s2,...) 也覆盖了 s1 的内容。

        在 gcc 上,-Wall 或 -Wstringop-overflow 将帮助您检测像这种情况,编译器知道源字符串的大小。但是,一般来说,编译器无法知道您的数据有多大。因此,您必须编写代码,确保不会复制超出您的空间。

        【讨论】:

        • strncat 的主要优点是它促使您考虑缓冲区大小的问题。但是,使用该函数而不是 strcat 绝不是灵丹妙药。你仍然需要执行完全相同的分析,如果你弄错了,你仍然会产生未定义的行为。
        • "Never" 可能是夸大其词 - 但话又说回来,也许不是:如果您能够做出保证,您几乎可以肯定知道初始的长度字符串,因此可以更有效地使用strcpy() 或memcpy()。
        • @TobySpeight 已编辑。我个人认为“永远不要使用strcat”对于初学者 C 程序员来说是有效的建议,据我所知,OP 才刚刚开始。
        • 我想我同意你的观点——你很少知道两个字符串的大小,但对总长度有一个上限。我能想到的唯一情况是您使用已知范围格式化数字(但当周围的代码发生变化时,即使这样也会让您发现)。
        • @JohnBollinger:我认为建议“永远不要使用strncat()”更为有效。你知道代码有什么问题吗:char data[16] = ""; strncat(data, "ABCDEFGHIJKLMNOP", sizeof(data));?大多数人没有意识到这是缓冲区溢出——这就是为什么我认为strncat() 是致命的。如果您对字符串的长度有足够的了解以能够安全地使用strncat(),那么您就可以使用memmove()(或memcpy()——但这是一个单独的讨论)来代替。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-12
        • 1970-01-01
        • 1970-01-01
        • 2022-11-21
        • 2017-01-24
        相关资源
        最近更新 更多