我发现了两种流行的编写安全复制函数的方法,这些方法是可移植的并符合C89标准。
示例1:
strncpy(dst, src, size);
dst[size - 1] = '\0';
示例 2:
dst[0] = '\0'
strncat(dst, src, size - 1);
这些方法提供完全相同的结果吗?或者在一个示例和另一个示例之间,
dst
的内容是否会有任何差异?是的,它们在技术上是不同的。尽管您可能不关心这个微不足道的差异。
例如,如果您像这样初始化:
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"方法设置所有尾部空字符可能会使程序稍微慢一些。
dst
的内容是否会有任何差异?strncpy
会在目标数组中填充零字节,直到达到完整的 size
字节。这通常是不必要和浪费的。
第一段代码片段两次设置了dst [size - 1]
。你可以改为:
strncpy(dst, src, size - 1);
dst[size - 1] = '\0';
大多数 C 程序员对 strncpy
的语义理解不清,使其令人困惑且易出错。建议即使在解决问题的情况下也避免使用此函数。
第二个使用 strncat
的代码片段对于较大的目标数组来说更节约空间:它只会修改必要的字节,但仍然不是最佳选择。
如果 size
为 0
,则两种方法均会失败并具有未定义的行为。
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
}
https://en.cppreference.com/w/cpp/string/byte/strncat
https://en.cppreference.com/w/cpp/string/byte/strncpy
#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;
}
}
size
是dst
的缓冲区大小,则size - 1
是正确的。 - cshusize == 0
,那么两者都无法正常工作;而且在这两个示例中,size
的含义是不同的。 - M.M