根据缓冲区长度将空终止字符数组复制到 std::string
Posted
技术标签:
【中文标题】根据缓冲区长度将空终止字符数组复制到 std::string【英文标题】:Copy null terminated char array to std::string respecting buffer length 【发布时间】:2016-08-29 13:31:00 【问题描述】:也许只是缺少咖啡,但我正在尝试从具有已知最大长度的空终止 char
数组创建一个 std::string
,但我不知道该怎么做。
auto s = std::string(buffer, sizeof(buffer));
.. 是我最喜欢的候选者,但由于 C++ 字符串不是以 null 结尾的,因此该命令将复制 sizeof(buffer)
字节,而不管包含任何“\0”。
auto s = std::string(buffer);
.. 从buffer
复制直到找到\0
。这几乎是我想要的,但我不能信任接收缓冲区,所以我想提供一个最大长度。
当然,我现在可以像这样集成strnlen()
:
auto s = std::string(buffer, strnlen(buffer, sizeof(buffer)));
但这看起来很脏 - 它遍历缓冲区两次,我必须处理像 string.h
和 strnlen()
这样的 C 工件(而且很丑)。
我将如何在现代 C++ 中做到这一点?
【问题讨论】:
它要么是空终止,要么长度正好是 sizeof(buffer),不能同时是两者。 这不是真的(我对赞成票感到惊讶)。为任何类型的写入函数提供的 C 缓冲区可以预先分配固定大小。写入缓冲区的序列仍然可以空终止。 无论如何这只是一个小问题。正确的术语是“可能以空结尾的最大长度 N 数组”或类似的东西。A C-buffer provided to any sort of writing function
对于这种情况,我会预先分配一个额外的字符 char buffer[n+1]; buffer[n] = 0;
。然后使用std::string(buffer)
,因为字符串始终以空值结尾。
或者只是在缓冲区的最后一个字节中粘贴 null 。最坏的情况是,您丢失了一个可能已经被截断的字符串。
【参考方案1】:
const char* end = std::find(buffer, buffer + sizeof(buffer), '\0');
std::string s(buffer, end);
【讨论】:
不是还要遍历缓冲区两次吗? @songyuanyao:看你怎么看,是的(一个遍历找到长度,然后一个遍历复制字节)。但这是不可避免的,因为在您检查长度之前您根本无法知道要分配的正确大小,并且您无法在分配之前复制字节。无论实际内容有多短,真正的单通道解决方案总是分配sizeof(buffer)
,这可能会显着增加内存消耗。
或者换一种说法,std::string s(buffer);
已经遍历了两次缓冲区,除非我们不介意过度分配,否则额外的大小限制没有用。原则上,您可以进行一系列大小呈指数增长的分配(例如vector
),但是当您将所有中间副本相加时,您仍然遍历了大约两次数据。
@joeeey:这是一个代码,在启用所有编译器警告的情况下干净地编译代码:godbolt.org/z/r56j5x - 如果您对其他版本有问题,请随时发布您的代码。
@joeeey 那是因为buffer
和end
需要是相同的类型。要么使用auto end
,要么像这样删除const
:godbolt.org/z/3TzTaK【参考方案2】:
这样的事情可以一次性完成..
auto eos = false;
std::string s;
std::copy_if(buffer, buffer + sizeof(buffer), std::back_inserter(s),
[&eos](auto v)
if (!eos)
if (v)
return true;
eos = true;
return false;
);
【讨论】:
除了在重新分配时复制字符所需的额外通行证? @T.C 不知道buffer
有多大,可能很难猜测是否会有任何重新分配?【参考方案3】:
如果您想要单通道解决方案,请从以下开始:
template<class CharT>
struct smart_c_string_iterator
using self=smart_c_string_iterator;
std::size_t index = 0;
bool is_end = true;
CharT* ptr = nullptr;
smart_c_string_iterator(CharT* pin):is_end(!pin || !*pin), ptr(pin)
smart_c_string_iterator(std::size_t end):index(end)
;
现在,对它进行修饰,使其成为一个完整的随机访问迭代器。大多数操作都非常简单(++
等应该同时推进 ptr
和 index
),除了 ==
和 !=
。
friend bool operator==(self lhs, self rhs)
if (lhs.is_end&&rhs.is_end) return true;
if (lhs.index==rhs.index) return true;
if (lhs.ptr==rhs.ptr) return true;
if (lhs.is_end && rhs.ptr && !*rhs.ptr) return true;
if (rhs.is_end && lhs.ptr && !*lhs.ptr) return true;
return false;
friend bool operator!=(self lhs, self rhs)
return !(lhs==rhs);
我们还需要:
template<class CharT>
std::pair<smart_c_string_iterator,smart_c_string_iterator>
smart_range( CharT* ptr, std::size_t max_length )
return ptr, max_length;
现在我们这样做:
auto r = smart_range(buffer, sizeof(buffer));
auto s = std::string(r.first, r.second);
在每个步骤中,我们都会在进行复制时检查缓冲区长度和空终止。
现在,Ranges v3 引入了哨兵的概念,它可以让您在执行上述操作的同时降低运行时成本。或者您可以手工制作等效的解决方案。
【讨论】:
以上是关于根据缓冲区长度将空终止字符数组复制到 std::string的主要内容,如果未能解决你的问题,请参考以下文章