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

Is there any difference between strncat and strncpy to write safe string copy function?

我遇到了两种流行的方法来编写可移植且符合 C89 的安全复制函数。

示例 1:

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

示例 2:

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

我的问题:

  1. 两者在技术上有什么区别吗?
  2. 是否有任何特殊情况,其中一个优于另一个?

是的,它们在技术上是不同的。虽然你可能不在乎这些细微的差别。

例如如果你这样初始化:

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

然后用你的 "Example 1",dst 变成 0x31 0x32 0x00 0x00 0x00 0x00 0x00 0x00

有了你的 "Example 2",dst 变成了 0x31 0x32 0x00 0x64 0x65 0x66 0x67 0x00

如果您只想复制字符串,那么差异无关紧要。

很难说哪个更好。但是在一个非常大的 size 和一个非常短的 src 的情况下,使用 "Example 1" 方法设置所有尾随空字符可能会使程序变慢一些。

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] = '[=10=]';
    strncat(dest, src, count); // at most count characters are copied from string src; 
                               // and then a terminating null character '[=10=]' 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 '[=10=]' 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 '[=10=]'.
    dest[count] = '[=10=]';        // 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 "[=10=]yz"
#define RES2A "[=10=][=10=][=10=]"
        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[=10=]z"
#define RES2B "1[=10=][=10=]"
        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[=10=]"
#define RES2C "12[=10=]"
        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;
    }
}