strncat和strncpy在编写安全字符串复制函数时有什么区别吗?

4

我发现了两种流行的编写安全复制函数的方法,这些方法是可移植的并符合C89标准。

示例1:

strncpy(dst, src, size);
dst[size - 1] = '\0';

示例 2:

dst[0] = '\0'
strncat(dst, src, size - 1);

这些方法提供完全相同的结果吗?或者在一个示例和另一个示例之间,dst 的内容是否会有任何差异?

它们都保证以相同的方式运行,因此它们都更好。 - Mad Physicist
1
你也可以尝试使用snprintf,它将为你提供一个稍微有用些的返回值,即写入的字符总数。 - bruceg
如果 sizedst 的缓冲区大小,则 size - 1 是正确的。 - cshu
@Griddoor 说得好。因此,一个潜在的问题实际上是如果 size == 0,那么两者都无法正常工作;而且在这两个示例中,size 的含义是不同的。 - M.M
这怎么可能是基于观点的呢?两个例子之间要么有差异,要么没有。这是一个是/否问题,因此是客观问题。正如被接受的答案所示,答案是:“是的,在这两个例子中有区别。” - Lone Learner
显示剩余2条评论
3个回答

4

是的,它们在技术上是不同的。尽管您可能不关心这个微不足道的差异。

例如,如果您像这样初始化:

char dst[] = "abcdefg";
char src[] = "12";
size_t size = sizeof dst;

使用你的"示例1",dst变成0x31 0x32 0x00 0x00 0x00 0x00 0x00 0x00

使用你的"示例2",dst变成0x31 0x32 0x00 0x64 0x65 0x66 0x67 0x00

如果你只想复制字符串,那么这两种方法的区别并不重要。

很难说哪一种更好。但是,在size非常大且src非常短的情况下,使用"示例1"方法设置所有尾部空字符可能会使程序稍微慢一些。


@M.M 我不知道大写字母为什么会被认为是不好的。不管怎样,我已经修改了。 - cshu

0
这些方法提供完全相同的结果吗?或者在一个示例和另一个示例之间,dst 的内容是否会有任何差异?
不完全一样:两个代码片段都可以执行字符串复制操作,并截断源字符串(包括空终止符),如果源字符串比目标数组长,则结果字符串将在目标数组中正确地以空终止符结尾。
但请注意以下几点:
  • strncpy 会在目标数组中填充零字节,直到达到完整的 size 字节。这通常是不必要和浪费的。

  • 第一段代码片段两次设置了dst [size - 1]。你可以改为:

      strncpy(dst, src, size - 1);
      dst[size - 1] = '\0';
    
  • 大多数 C 程序员对 strncpy 的语义理解不清,使其令人困惑且易出错。建议即使在解决问题的情况下也避免使用此函数。

  • 第二个使用 strncat 的代码片段对于较大的目标数组来说更节约空间:它只会修改必要的字节,但仍然不是最佳选择。

  • 如果 size0,则两种方法均会失败并具有未定义的行为。

哪个更可取:
第二个,但使用自定义函数处理所有情况是更好的解决方案。该函数可以正确处理所有情况,并且不执行冗余的复制、测试或填充操作,同时为调用者返回有用的值以检测截断。
以下是一个简单的实现:
size_t safe_strcpy(char *dest, size_t dest_size, const char *src) {
    size_t i = 0;
    if (dest_size) {
        for (; i < dest_size - 1; i++) {
            if ((dest[i] = src[i]) == '\0')
                return i;
        }
        dest[i++] = '\0';  // perform truncation
    ]
    return i;  // return dest_size
}

使用方式如下:

void test_function(const char *str) {
    char buf[200];

    // copy with truncation
    safe_strcpy(buf, sizeof buf, str);

    // copy with truncation and test
    if (safe_strcpy(buf, sizeof buf, str) < sizeof buf) {
        // no truncation
    } else {
        // truncation occurred
    }

    // safe concatenation:
    size_t pos = 0;
    pos += safe_strcpy(buf + pos, sizeof(buf) - pos, str);
    pos += safe_strcpy(buf + pos, sizeof(buf) - pos, str);
    pos += safe_strcpy(buf + pos, sizeof(buf) - pos, str);
    pos += safe_strcpy(buf + pos, sizeof(buf) - pos, str);
    if (pos < sizeof buf) {
        // str was replicated 4 times successfully
    } else {
        // truncation occurred, strlen(buf) is 199
    }
}

为了完整起见,这里是拼接对应的函数:

size_t safe_strcat(char *dest, size_t dest_size, const char *src) {
    size_t i = 0;
    if (dest_size) {
        while (i < dest_size - 1 && dest[i] != '\0')
            i++;
        for (; i < dest_size - 1; i++) {
            if ((dest[i] = src[i]) == '\0')
                return i;
        }
        dest[i++] = '\0';  // perform truncation
    ]
    return i;  // return dest_size
}

-2

https://en.cppreference.com/w/cpp/string/byte/strncat
https://en.cppreference.com/w/cpp/string/byte/strncpy

这是一个 C++(哈哈哈)的例子,说明它们之间的区别:
https://wandbox.org/permlink/igKe0CtdyuMw1JHL
#include <iostream>
#include <cstring>
#include <cassert>

//////////////////////////////////////////////////////////
// using strncat
// https://en.cppreference.com/w/cpp/string/byte/strncat

template <std::size_t N>
inline void safestrcpy1(char *dest, const char *src)
{
    constexpr std::size_t count = N-1;
    dest[0] = '\0';
    strncat(dest, src, count); // at most count characters are copied from string src; 
                               // and then a terminating null character '\0' is written if it was not yet encountered
}

template <std::size_t N>
inline void safestrcpy1(char (&dest)[N], const char *src)
{
    safestrcpy1<N>(&dest[0], src);
}

//////////////////////////////////////////////////////////
// using strncpy
// https://en.cppreference.com/w/cpp/string/byte/strncpy

template <std::size_t N>
inline void safestrcpy2(char *dest, const char *src)
{
    constexpr std::size_t count = N-1;
    strncpy(dest, src, count); // null termination '\0' is only written if it occurs somewhere within src[0] .. src[count-1]
                               // if it occurs at src[i] and i < count, then dest[i+1], dest[i+2], ... dest[count-1] are also set to '\0'.
    dest[count] = '\0';        // ensure null termination
}

template <std::size_t N>
inline void safestrcpy2(char (&dest)[N], const char *src)
{
    safestrcpy2<N>(&dest[0], src);
}


//////////////////////////////////////////////////////////
// main
//
int main()
{
    {
        char dest1[4] = "xyz";
        char dest2[4] = "xyz";
        safestrcpy1(dest1, "");
        safestrcpy2(dest2, "");
#define RES1A "\0yz"
#define RES2A "\0\0\0"
        assert(memcmp(dest1, RES1A, sizeof(RES1A)) == 0);
        assert(memcmp(dest2, RES2A, sizeof(RES2A)) == 0);
        std::cout << dest1 << std::endl;
    }
    {
        char dest1[4] = "xyz";
        char dest2[4] = "xyz";
        safestrcpy1(dest1, "1");
        safestrcpy2(dest2, "1");
#define RES1B "1\0z"
#define RES2B "1\0\0"
        assert(memcmp(dest1, RES1B, sizeof(RES1B)) == 0);
        assert(memcmp(dest2, RES2B, sizeof(RES2B)) == 0);
        std::cout << dest1 << std::endl;
    }
    {
        char dest1[4] = "xyz";
        char dest2[4] = "xyz";
        safestrcpy1(dest1, "12");
        safestrcpy2(dest2, "12");
#define RES1C "12\0"
#define RES2C "12\0"
        assert(memcmp(dest1, RES1C, sizeof(RES1C)) == 0);
        assert(memcmp(dest2, RES2C, sizeof(RES2C)) == 0);
        std::cout << dest1 << std::endl;
    }
    {
        char dest1[4] = "xyz";
        char dest2[4] = "xyz";
        safestrcpy1(dest1, "123");
        safestrcpy2(dest2, "123");
#define RES1D "123"
#define RES2D "123"
        assert(memcmp(dest1, RES1D, sizeof(RES1D)) == 0);
        assert(memcmp(dest2, RES2D, sizeof(RES2D)) == 0);
        std::cout << dest1 << std::endl;
    }
    {
        char dest1[4] = "xyz";
        char dest2[4] = "xyz";
        safestrcpy1(dest1, "1234");
        safestrcpy2(dest2, "1234");
#define RES1E "123"
#define RES2E "123"
        assert(memcmp(dest1, RES1E, sizeof(RES1E)) == 0);
        assert(memcmp(dest2, RES2E, sizeof(RES2E)) == 0);
        std::cout << dest1 << std::endl;
    }
}

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