如何在C语言中覆盖字符串的一部分?

3

比如说,我有一个字符串。我只想改变字符串的前几个字符,而让其余部分保持不变。在C语言中,最好的方法是什么?

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

int main() {
    char src[40];
    char src2[40];
    char dest[12];

    memset(dest, '\0', sizeof(dest));
    strcpy(src, "This is a string");
    strcpy(src2, "That");
    strncpy(dest, src, sizeof(src));
    strncpy(dest, src2, sizeof(src2));

    printf("Final copied string : %s\n", dest);
}

我希望将字符串从"This is a string"更改为"That is a string"

是否有一种简单的方法可以实现这个目标?


你需要对C语言中字符串的工作原理和终止方式进行一些研究。同时,你还需要理解字符串长度与字符串所在数组大小之间的区别。 - klutt
3
这段代码有误:strncpy(dest, src, sizeof(src)); 使用源字符串的大小作为限制是无效的。重要的是目标字符串的大小。dest 只能容纳长度不超过11个字符的字符串,而 src 可以容纳长度达39个字符的字符串。由于 src 包含一个超过11个字符的字符串,因此会出现缓冲区溢出,导致未定义行为。 - Gerhardh
1
不完全正确,@Gerhardh。如果strncpy()的长度参数不大于源字符串的实际长度,则strncpy()不会复制终止符,这正是OP想要的。 - John Bollinger
@Gerhardh 不好意思,已修复。 - klutt
@JohnBollinger 请注意,我指的是第一个调用。还可能有什么其他目的?但我同意,这并不是函数的真正预期用途。 - Gerhardh
显示剩余4条评论
4个回答

8
这里有几个问题。
首先,`dest` 只有12个字节长,太短了无法容纳 "This is a string"。试图将该字符串复制到 `dest` 中会溢出缓冲区。这会引发未定义行为。请至少将其设置为20个字节。
其次,`sizeof(src)` 给出整个数组的大小,即40,而不是字符串的长度。如果目标缓冲区不够大,这也会产生未定义的行为。应使用 `strlen`。对于 `sizeof(src2)` 也是同样的情况。
经过这些更改,您应该有以下代码:
#include <stdio.h>
#include <string.h>

int main() {
    char src[40];
    char src2[40];
    char dest[20];

    memset(dest, '\0', sizeof(dest));
    strcpy(src, "This is a string");
    strcpy(src2, "That");
    strncpy(dest, src, strlen(src));
    strncpy(dest, src2, strlen(src2));

    printf("Final copied string : %s\n", dest);
}

非常感谢!将“sizeof”更改为“strlen”正是我所需要的。 - spiderwebdev
1
@spiderwebdev 不要忘记让 dest 足够大以容纳字符串。 - dbush
所有这些逻辑都假定 sizeof(dest) - 1 >= strlen(src) >= strlen(src2)。由于您知道 src 和 src2 的长度,因此无需调用 memset()。将第一个 strncpy 修改为 strcpy 可以消除对 memset 的冗余调用。但是,如果这是为更通用的函数做准备的思维练习,则某些逻辑过于天真。 - Gardener

2

sizeof(src2) 的值是 40(它是整个数组的大小)- 你可能想要使用 strlen(src2)(它仅表示字符串中实际使用的字符数):

strncpy(dest, src2, strlen(src2));

顺便提一下,你的代码存在缓冲区溢出问题:数组 dest 的大小不足以容纳结果字符串。它至少需要有17个字符的空间来容纳字符串"This is a string"。你还需要使用:

strncpy(dest, src, strlen(src));

1
@Broman:除非我误解了问题,它是可以的。你试过了吗? - Sander De Dycker
是的,我尝试了你的代码。输出是“ring is a string”。 - klutt
1
这正是我所做的,由于我们得到了不同的结果,你的代码包含未定义的行为。 - klutt
@Broman:观察得很好 - 尽管未定义的行为在原始代码中(缓冲区溢出)。我会相应地调整我的答案。 - Sander De Dycker
不需要调整。dbush已经给出了正确的答案。如果你没有什么要补充的,最好就删除这个回复。 - klutt
@Broman:虽然缓冲区溢出是一个值得指出的问题,但我认为它并不是回答所提问题的核心(很可能是在创建最小可复现示例时引入的)。 - Sander De Dycker

1

我对 memset() 的调用感到困惑和有些担心。 memset 的调用允许使用 strncpy() 而无需担心终止符 '\0',这样的调用假定此逻辑将在被从更高级别函数调用的通用函数中使用。

如果不是这种情况,则第一个 strncpy() 应该被替换为 strcpy,而 memset 应该被放弃:

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

int main() {
    char src[40];
    char src2[40];
    char dest[20];

    /* memset(dest, '\0', sizeof(dest)); -- No need for this */
    strcpy(src, "This is a string");
    strcpy(src2, "That");
    strcpy(dest, src, strlen(src)); /* strcpy will put a '\0' at the end */
    strncpy(dest, src2, strlen(src2));

    printf("Final copied string : %s\n", dest);
}

然而,如果这将从更高级别的函数调用,则我们确实需要检查传入字符串的长度和/或malloc目标缓冲区并释放它。memset()是一个让人不得不考虑更多逻辑的关键点。否则,只需用strcpy()替换第一个strncpy()。

1

在@dbush的回答基础上进一步扩展:

我认为代码仍然非常脆弱。

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

int main() {
    char src[40];
    char src2[40];
    char dest[20];

这里第一个陷阱被创建:dest比两个源都要小。虽然这可以处理,但需要在随后的代码中更加小心谨慎。最好让目标始终足够大以容纳源。

memset(dest, '\0', sizeof(dest)); //Good

但是,最好也将其他数组初始化为0。

memset(src, '\0', sizeof(src));
memset(src2, '\0', sizeof(src2));

顺带一提:当然您可以使用数组初始化语法来避免使用memset的麻烦:

    char src[40] = {0};
    char src2[40] = {0};
    char dest[20] = {0};

下面两行代码存在潜在的危险,因为实际情况中这两个字符串可能不是字符串常量(即用户输入等)。甚至对于字符串常量也没有提供长度检查……更好的做法是:

//    strcpy(src, "This is a string");
//    strcpy(src2, "That");

strncpy(src, "This is a string", sizeof(src)- 1);
strncpy(src2, "That", sizeof(src2) -1);

那样我们可以确保不会造成任何溢出。我们还要确保 src 和 src2 中的字符串被正确地以空字符结尾。现在,将 src/src2 复制到 dest 也是危险的。我们必须确保不会溢出 dest。
//strncpy(dest, src, strlen(src));
//strncpy(dest, src2, strlen(src2));

更好的写法:
strncpy(dest, src, sizeof(dest) - 1);
strncpy(dest, "That is a long rubbish string that easily could overflow dest", sizeof(dest) -1);

我们只复制dest可以容纳的内容并保留空终止符。
现在来替换前X个字符。同样,我们必须确保没有溢出发生。我们使用strlen来确定src2中空终止字符串的实际大小,但需要使用/评估dest的最大大小。因此,strlen和sizeof的混合使用。
memcpy仅供娱乐。您也可以同样使用strncpy。
memcpy(dest, src2, strlen(src2) < sizeof(dest) ? strlen(src2) : sizeof(dest));

printf("Final copied string : %s\n", dest);

}

所以整个安全实现看起来像这样:
#include <stdio.h>
#include <string.h>

int main() {
    char src[40] = {0};
    char src2[40] = {0};
    char dest[20] = {0};


    strncpy(src, "This is a string", sizeof(src)- 1);

    strncpy(src2, "That is a long rubbish string and sooooooooooooooooooooooooo much more", sizeof(src2) -1);

    strncpy(dest, src, sizeof(dest) - 1);

    memcpy(dest, src2, strlen(src2) < sizeof(dest) ? strlen(src2) : sizeof(dest));

    printf("Final copied string : %s\n", dest);
}

请注意,使用sizeof只在char类型上等同于strlen;在其他类型上需要进行更多操作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接