C语言 - 使用char *进行memcpy时,长度大于源字符串长度的情况

3

我现在有以下的 C 代码

int length = 50
char *target_str = (char*) malloc(length);
char *source_str = read_string_from_somewhere() // read a string from somewhere
                                                //    with length, say 20
memcpy(target_str, source_str, length);

情景是target_str被初始化为50个字节。source_str是长度为20的字符串。
如果我想将source_str复制到target_str,我会使用上面所述的长度为50的memcpy(),这是target_str的大小。我在memcpy中使用length的原因是,source_str的最大值可以是length,但通常小于该值(在上面的示例中为20)。
现在,如果我想根据其终止字符('\0')复制source_str的长度,即使memcpy长度超过终止字符的索引,上面的代码是否正确?还是有其他建议?
谢谢任何帮助。

2
为什么不使用 strlen(source_str),然后您可以分配确切的大小呢?此外,您不需要强制转换 malloc 的结果。 - ggorlen
1
Add +1 for null - Ed Heal
可能是在C语言中访问数组末尾之外的元素的重复问题。 - Mgetz
5个回答

3
情境是使用50个字节初始化target_str。source_str是长度为20的字符串。
如果我想将source_str复制到target_str中,我使用上面的memcpy()函数,长度为50,即target_str的大小。
目前,您要求memcpy读取源字符串结束后的30个字符,因为它不关心源字符串可能的空终止符,这是一种未定义的行为。
由于你要复制一个字符串,所以你可以使用strcpy而不是memcpy。
但是问题的大小可以反转,我的意思是目标可以比源小,而且没有保护,你会再次遇到未定义的行为。
因此,您可以使用strncpy给出目标的长度,只需注意在目标小于源的情况下需要添加最终的空字符:
int length = 50
char *target_str = (char*) malloc(length);
char *source_str = read_string_from_somewhere(); // length unknown

strncpy(target_str, source_str, length - 1); // -1 to let place for \0
target_str[length - 1] = 0; // force the presence of a null character at end in case

2
如果我想将source_str复制到target_str中,我会使用memcpy(),并设置长度为50,即target_str的大小。之所以在memcpy()中使用长度是因为source_str的最大值可以是长度,但通常小于该值(在上面的示例中长度为20)。
非常重要的是要区分:
- 指向source_str的数组的大小 - 如果有的话,指向source_str的字符串的长度(+/-终止符)
如果source_str肯定指向长度为50或更长的数组,则您提出的memcpy()方法可以使用。否则,当source_str实际上指向较短的数组时,它会产生未定义的行为。可能发生任何C实现的结果。
如果source_str确定指向不超过长度-1个字符的(正确终止的)C字符串,并且您要复制的是其字符串值,则strcpy()比memcpy()更自然。它将复制所有字符串内容,包括终止符。当source_str指向长度小于length的数组时,没有问题,前提是它包含字符串终止符。
如果以上两种情况都不确定,那么您要做什么就不清楚了。strncpy()函数可能涵盖其中一些情况,但并非所有情况。

1

memcpy用于复制固定大小的内存块,因此如果要复制以'\n'结尾的较短内容,则不应使用memcpy。

还有其他类似的函数,如strncpy或strlcpy,可以执行类似的操作。最好检查实现的功能。出于可读性的考虑,我从原始源代码中删除了优化版本。

以下是一个memcpy实现示例:https://git.musl-libc.org/cgit/musl/tree/src/string/memcpy.c

void *memcpy(void *restrict dest, const void *restrict src, size_t n)
{
    unsigned char *d = dest;
    const unsigned char *s = src;
    for (; n; n--) *d++ = *s++;
    return dest;
}

很明显,这里访问了两个内存块 n 次。无论源字符串或目标字符串的大小如何,这都会导致将内存复制到字符串之外,如果字符串较短,则会出现问题,并可能导致各种不必要的行为。这是从 https://git.musl-libc.org/cgit/musl/tree/src/string/strlcpy.c 中的 strlcpy。
size_t strlcpy(char *d, const char *s, size_t n)
{
    char *d0 = d;
    size_t *wd;

    if (!n--) goto finish;
    for (; n && (*d=*s); n--, s++, d++);
    *d = 0;
finish:
    return d-d0 + strlen(s);
}

这里的诀窍在于n && (*d = 0)计算结果为false,会打破循环条件并提前退出。因此,这会给你想要的行为。

1
现在,假如我想根据终止字符('\0')复制source_str的长度,即使memcpy的长度超过终止字符的索引,上述代码是否是正确的方法?
不是的;即使空字符在所指向的字符串的分配空间结束之前出现,你仍将复制整个source_str的内容,甚至超过了空字符。
如果你关心程序使用的辅助空间最小化,你可以使用strlen来确定source_str的长度,并基于此分配target_str。另外,strcpy类似于memcpy,但专门用于以空字符结尾的字符串(请注意,它没有“size”或“length”参数):
char *target_str = NULL;
char *source_str = read_string_from_somewhere();
size_t len = strlen(source_str);

target_str = malloc(len + 1);

strcpy(target_str, source_str);

// ...

free(target_str);
target_str = NULL;

0

使用 strlen 函数来确定 source_string 的确切大小,并相应地进行分配,记得为空终止符添加一个额外的字节。以下是完整示例:

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

int main(void) {
    char *source_str = "string_read_from_somewhere";

    int len = strlen(source_str);
    char *target_str = malloc(len + 1);

    if (!target_str) {
        fprintf(stderr, "%s:%d: malloc failed", __FILE__, __LINE__);
        return 1;
    }

    memcpy(target_str, source_str, len + 1);

    puts(target_str);
    free(target_str);

    return 0;
}

此外,没有必要强制转换malloc的结果。不要忘记free已分配的内存。

正如评论中提到的那样,您可能希望将malloc字符串的大小限制在合理的范围内。


每当我看到裸露的 strlen() 结果被放入 malloc()memcpy() 中时,我总是感到一种力量的干扰。这是一个等待发生的漏洞 :) 但它确实回答了问题。 - Michael Dorgan

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