"strcat function in C assumes the destination string is large enough to hold contents of source string and its own." 中的混乱

Confusion in "strcat function in C assumes the destination string is large enough to hold contents of source string and its own."

所以我读到要谨慎使用 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;
}

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

strcat() function is indeed to be used carefully because it doesn't protect you from anything. If the source string isn't NULL-terminated, the destination string isn't NULL-terminated, or the destination string doesn't have enough space, strcat will still copy data. Therefore, it is easy to overwrite data you didn't mean to overwrite. It is your responsibility to make sure you have enough space. Using 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,因此理想情况下输出应该是 --。但是,由于编译器恰好在内存中安排了s1s2,所以我实际得到的输出是-OOPS WAY TOO LONG-strcpy(s2,...) 也覆盖了 s1 的内容。

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

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

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

使 C 语言快速的原因之一是它不会检查以确保您遵守规则。它只是告诉你规则并假设你遵守它们,如果你不遵守,坏事可能会发生也可能不会发生。在您的特定情况下,它似乎有效,但没有 gua运行tee of that.

例如,当我 运行 你的第二段代码时,它似乎也有效。但是如果我把它改成这样:

#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;
}

程序崩溃。

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

是的,strcat 的第一个参数必须是指向足以存储串联结果的内存的指针。在您的两个程序中,都违反了该要求。由于这两个程序中都没有错误消息,您可能会有这样的印象,即规则可能不是您认为的那样,即使第一个参数不是指针,以某种方式调用 strcat 也是有效的到足够的内存。但不,事实并非如此:当内存不足时调用 strcat 肯定是错误的。没有错误消息,或者一个或两个程序出现 "work",这一事实证明不了什么。

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

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

两个片段都调用了未定义的行为——第一个是因为srcdest没有初始化指向任何有意义的地方,第二个是因为你正在写超过数组末尾。

C 不会对数组访问强制执行任何类型的边界检查 - 如果您尝试写入超出数组末尾的内容,则不会得到 "Index out of range" 异常。如果您尝试越过页面边界或破坏一些重要的东西(如帧指针),您 可能 会遇到运行时错误,但否则您只会冒损坏程序中数据的风险。

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

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

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

    char dest[15] = "Hello DEFGIJK";

...并在 src 中复制 10 个字节的 11 个字节:

    char src[11] = "Hello ABC";

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

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

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