我遇到了两种流行的方法来编写可移植且符合 C89 的安全复制功能。
例一:
strncpy(dst, src, size);
dst[size - 1] = '\0';
例二:
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”的方法设置所有尾随空字符可能会使程序变慢一些。
如果源字符串比目标数组长,则两个代码片段都可以执行截断的字符串复制操作,包括空终止符,并且目标数组中的结果字符串将被正确地以空终止。
但是请注意这些评论:
strncpy
还将用空字节填充目标数组,直到完整的 size
字节。这通常是不必要和浪费的。
第一个代码片段设置了两次
dst[size - 1]
。你可以改写:
strncpy(dst, src, size - 1);
dst[size - 1] = '\0';
大多数 C 程序员对
strncpy
的语义知之甚少,这使它变得混乱和容易出错。即使在解决问题的情况下,也建议避免使用此功能。
第二个代码片段,使用
strncat
对于大型目标数组来说浪费较少,但仍然不是最优的。
两种方法都会失败并且具有未定义的行为是
size
是0
.
这些方法是否提供完全相同的结果,或者一个示例与另一个示例之间的
内容是否有任何差异?dst
不,不完全是,如上所述
哪个更好:
第二个,但更好的解决方案是编写一个自定义函数,它可以正确处理所有情况,不执行冗余复制或填充,并返回一个指示器供调用者检测截断。
这里是一个简单的实现:
size_t safe_strcpy(char *dest, size_t dest_size, const char *src) {
size_t i;
for (i = 0; i + 1 < dest_size; i++) {
if ((dest[i] = src[i]) == '\0')
return i;
}
if (dest_size)
dest[i] = '\0';
return i;
}
这样使用:
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
}
}
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;
}
}