繁体   English   中英

“C语言中的strcat函数混淆假设目标字符串足够大,可以保存源字符串及其自身的内容。”

[英]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函数无法确切知道目标缓冲区的长度,因此它假定传递给它的缓冲区足够大。 如果不是,则通过写入缓冲区的末尾来调用未定义的行为 这就是第二段代码中发生的事情。

第一段代码也是无效的,因为srcdest都是未初始化的指针。 当它们传递给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;
}

程序崩溃了。

我认为你的困惑实际上并不是关于strcat的定义。 您真正的困惑是您认为C编译器会强制执行所有“规则”。 这个假设是非常错误的。

是的, strcat的第一个参数必须是一个指向内存的指针,足以存储连接的结果。 在您的两个程序中,都违反了该要求。 你可能会从任何一个程序中缺少错误消息得到这样的印象:也许规则不是你想象的那样,即使第一个参数不是指向足够内存的指针,它也会以某种方式调用strcat 但不是,情况并非如此:当内存不足时调用strcat肯定是错误的。 没有错误消息,或者一个或两个程序似乎“正常”的事实证明没有任何证据。

这是一个类比。 (你小时候甚至可能有这种经历。)假设你的母亲告诉你不要跑到街对面,因为你可能会被车撞到。 无论如何,假设你跑到街对面,不要被车撞到。 你是否认为你母亲的建议不正确? 这是一个有效的结论吗?

总之,您阅读的内容是正确的:必须小心使用strcat 但是,让我们换一种说法:打电话时,你一定要小心strcat 如果你不小心,各种各样的事情都可能出错,没有任何警告。 事实上,许多风格指南建议不要使用strcat功能,因为如果你不小心它们就很容易被误用。 (只要你小心, strcat功能就可以完全安全地使用 - 但当然并非所有程序员都非常小​​心。)

确实要小心使用strcat()函数 ,因为它不能保护您免受任何伤害。 如果源字符串不以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 ,所以理想情况下输出应该是-- 但是,由于编译器如何在内存中排列s1s2 ,我实际得到的输出是-OOPS WAY TOO LONG- strcpy(s2,...)覆盖了s1的内容。

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

两个片段都调用未定义的行为 - 第一个因为srcdest未初始化为指向任何有意义的行为,第二个因为您正在写入数组的末尾。

C不会对数组访问强制执行任何类型的边界检查 - 如果您尝试写入数组的末尾,则不会获得“索引超出范围”异常。 如果您尝试访问页面边界或重写某些重要内容(如帧指针),则可能会出现运行时错误,但您只是冒着破坏程序中数据的风险。

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

我想指出第二个程序实际发生了什么,以说明问题。

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

    char dest[15] = "Hello DEFGIJK";

...在src上有11个字节,其中复制了10个字节:

    char src[11] = "Hello ABC";

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

请注意,strcat()函数对于您在dest(或src)上分配的内存量一无所知。 您可以确保在dest上分配了足够的内存以防止内存损坏。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM