【发布时间】:2011-02-22 18:47:35
【问题描述】:
我想将一个字符串复制到一个 char 数组中,而不是超出缓冲区。
所以如果我有一个大小为 5 的 char 数组,那么我想从一个字符串中复制最多 5 个字节。
执行此操作的代码是什么?
【问题讨论】:
-
std::string,不知道其他类型的字符串。
-
有字符串、一些或多或少知名的库字符串和不计其数的本土实现。
我想将一个字符串复制到一个 char 数组中,而不是超出缓冲区。
所以如果我有一个大小为 5 的 char 数组,那么我想从一个字符串中复制最多 5 个字节。
执行此操作的代码是什么?
【问题讨论】:
更新:我想我会尝试将一些答案联系在一起,这些答案让我确信我自己最初的下意识 strncpy 反应很差。
首先,正如 AndreyT 在该问题的 cmets 中指出的那样,截断方法(snprintf、strlcpy 和 strncpy)通常不是一个好的解决方案。根据缓冲区长度检查字符串 string.size() 的大小并返回/抛出错误或调整缓冲区大小通常会更好。
如果在您的情况下截断是可以的,恕我直言,strlcpy 是最好的解决方案,它是确保空终止的最快/最少开销的方法。不幸的是,它不在许多/所有标准发行版中,因此不可移植。如果你做了很多这些,也许值得提供你自己的实现,AndreyT 给了一个example。它以 O(result length) 运行。参考规范还返回复制的字节数,这有助于检测源是否被截断。
其他好的解决方案是sprintf 和snprintf。它们是标准的,因此是可移植的,并提供安全的空终止结果。它们比 strlcpy 有更多的开销(解析格式字符串说明符和变量参数列表),但除非你做了很多这些,否则你可能不会注意到差异。它也以 O(result length) 运行。 snprintf 总是安全的,如果格式说明符错误,sprintf 可能会溢出(正如其他人所指出的,格式字符串应该是"%.<N>s" 而不是"%<N>s")。这些方法还返回复制的字节数。
一个特殊的解决方案是strncpy。它以 O(buffer length) 运行,因为如果它到达 src 的末尾,它会将缓冲区的剩余部分归零。仅当您需要将缓冲区的尾部归零或确信目标和源字符串长度相同时才有用。另请注意,它并不安全,因为它不一定为空终止字符串。如果源被截断,则不会追加 null,因此使用 null 赋值依次调用以确保 null 终止:strncpy(buffer, str.c_str(), BUFFER_LAST); buffer[BUFFER_LAST] = '\0';
【讨论】:
strncpy 更快或更慢取决于源字符串和目标缓冲区的相对大小。由于strncpy 在缓冲区较大的情况下会做大量浪费工作,因此在一般情况下滥用strncpy 不仅速度较慢,而且严重地变慢,速度要慢几个数量级。在这个例子中,唯一能节省时间的是不切实际的小目标缓冲区(只有 5 个字符)。
sprintf/snprintf 确实不是最有效的函数,原因很明显。但这仅意味着人们必须更喜欢使用类似strlcpy 的功能,而不是几乎没用的strncpy。最后,在启用截断的字符串复制上下文中性能的重要性是另一个问题。被视为可接受的字符串截断通常表示用户界面应用程序。谁需要用户界面的性能?
std::string my_string("something");
char* my_char_array = new char[5];
strncpy(my_char_array, my_string.c_str(), 4);
my_char_array[4] = '\0'; // my_char_array contains "some"
使用strncpy,您最多可以将n 个字符从源复制到目标。但是,请注意,如果源字符串的长度最多为 n 个字符,则目标字符串不会以 null 结尾;您必须自己将终止的空字符放入其中。
长度为 5 的 char 数组最多可以包含 4 个字符的字符串,因为第 5 个必须是终止空字符。因此在上面的代码中,n = 4.
【讨论】:
一些不错的 libc 版本提供了非标准但很好的替代 strcpy(3)/strncpy(3) - strlcpy(3)。
【讨论】:
std::string str = "Your string";
char buffer[5];
strncpy(buffer, str.c_str(), sizeof(buffer));
buffer[sizeof(buffer)-1] = '\0';
最后一行是必需的,因为 strncpy 不能保证 NUL 终止字符串(昨天已经讨论过动机)。
如果你使用宽字符串,而不是sizeof(buffer),你会使用sizeof(buffer)/sizeof(*buffer),或者更好的是,像宏
#define ARRSIZE(arr) (sizeof(arr)/sizeof(*(arr)))
/* ... */
buffer[ARRSIZE(buffer)-1]='\0';
【讨论】:
首先,strncpy 几乎肯定不是你想要的。 strncpy 是为相当特定的目的而设计的。它在标准库中几乎完全是因为它已经存在,而不是因为它通常很有用。
做你想做的最简单的方法可能是:
sprintf(buffer, "%.4s", your_string.c_str());
与strncpy 不同,这保证了结果将被 NUL 终止,但如果源比指定的短,则不会在目标中填充额外数据(尽管当目标长度为5).
【讨论】:
strncpy(buffer, str.c_str(), 4); buffer[4] = '\0';,这不是很多不必要的开销吗?
sprintf 解决方案,它更简单一些。只有当性能不足时,我才会分析,也许会发现这是一个问题,用strncpy 测试它,也许会发现它工作得更好。
snprintf,它允许您指定目标缓冲区大小。
strncpy 更快或更慢取决于源字符串和目标缓冲区的相对大小。由于strncpy 在缓冲区较大的情况下会浪费大量的工作,因此在一般情况下滥用strncpy 不仅更慢,而且极其缓慢,慢了几个数量级.在这个例子中,唯一能节省时间的是不切实际的小目标缓冲区(只有 5 个字符)。
snprintf 当时不是 C++ 的一部分。即便如此,在此处指定精度会限制可以写入缓冲区的数据量,防止缓冲区溢出(没有指定大于缓冲区的大小 - 如果您这样做,snprintf 不会防止缓冲区溢出。
使用函数 损坏的链接,并且在目标站点上找不到材料,如果您的实现提供了一个(该函数不在标准 C 库中),但它被广泛接受为一个事实上的标准名称,用于零终止字符串的“安全”有限长度复制函数。strlcpy
如果您的实现不提供strlcpy 功能,请自行实现。例如,这样的事情可能对你有用
char *my_strlcpy(char *dst, const char *src, size_t n)
{
assert(dst != NULL && src != NULL);
if (n > 0)
{
char *pd;
const char *ps;
for (--n, pd = dst, ps = src; n > 0 && *ps != '\0'; --n, ++pd, ++ps)
*pd = *ps;
*pd = '\0';
}
return dst;
}
(实际上,事实上接受的strlcpy 返回size_t,因此您可能更愿意实现接受的规范而不是我上面所做的)。
请注意建议为此使用strncpy 的答案。 strncpy 不是安全的有限长度字符串复制功能,不应该用于该目的。虽然您可以强制 strncpy 为此目的“工作”,但它仍然类似于用锤子驱动木螺丝。
【讨论】:
如果你总是有一个大小为 5 的缓冲区,那么你可以这样做:
std::string s = "Your string";
char buffer[5]={s[0],s[1],s[2],s[3],'\0'};
编辑: 当然,假设你的 std::string 足够大。
【讨论】:
这正是std::string 的复制功能所做的。
#include <string>
#include <iostream>
int main()
{
char test[5];
std::string str( "Hello, world" );
str.copy(test, 5);
std::cout.write(test, 5);
std::cout.put('\n');
return 0;
}
如果你需要空终止,你应该这样做:
str.copy(test, 4);
test[4] = '\0';
【讨论】:
warning C4996: 'std::basic_string<_Elem,_Traits,_Alloc>::copy': Function call with parameters that may be unsafe - this call relies on the caller to check that the passed values are correct. To disable this warning, use -D_SCL_SECURE_NO_WARNINGS. See documentation on how to use Visual C++ 'Checked Iterators'
char mystring[101]; // a 100 character string plus terminator
char *any_input;
any_input = "Example";
iterate = 0;
while ( any_input[iterate] != '\0' && iterate < 100) {
mystring[iterate] = any_input[iterate];
iterate++;
}
mystring[iterate] = '\0';
这是基本的高效设计。
【讨论】:
我认为 snprintf() 是非常安全和最简单的
snprintf ( buffer, 100, "The half of %d is %d", 60, 60/2 );
空字符会自动结束:)
【讨论】:
void stringChange(string var){
char strArray[100];
strcpy(strArray, var.c_str());
}
我想这应该可行。它会将表单字符串复制到 char 数组中。
【讨论】:
最流行的答案很好,但空终止不是通用的。以空值终止字符缓冲区的通用方法是:
std::string aString = "foo";
const size_t BUF_LEN = 5;
char buf[BUF_LEN];
size_t len = aString.copy(buf, BUF_LEN-1); // leave one char for the null-termination
buf[len] = '\0';
len 是复制的字符数,因此它在0 和BUF_LEN-1 之间。
【讨论】: