根据缓冲区长度将空终止字符数组复制到 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.hstrnlen() 这样的 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 那是因为bufferend 需要是相同的类型。要么使用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) 
;

现在,对它进行修饰,使其成为一个完整的随机访问迭代器。大多数操作都非常简单(++ 等应该同时推进 ptrindex),除了 ==!=

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的主要内容,如果未能解决你的问题,请参考以下文章

C++ char 数组空终止符位置

如何在不将空值发送到输出数组的情况下拆分字符串

c++中用new给未知大小的数组分配空间怎么弄?

如何避免 strcpy_s 缓冲区太小

编写一个C语言的内存拷贝函数,把源地址的指定长度的数据拷贝到目标地址,考虑8,16,32位数据位宽

ByteBuffer 和字节数组