首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >for-loop-copy vs std::copy中的Bug,我不明白

for-loop-copy vs std::copy中的Bug,我不明白
EN

Stack Overflow用户
提问于 2021-10-07 08:12:39
回答 1查看 64关注 0票数 0

下面是一个用于串行输出接口的非常简单(从大部分模板代码中去掉)的fifo缓冲区。我决定编写自己的代码,因为a)我正在尝试学习C++,b)容器适配器queue没有带来“快速放多”功能,它为每个元素强制执行一个显式的push()。我知道许多现代编译器在许多情况下可能会优化这一点,但出于一个原因,我想自己做这件事--请随意评论这个想法和任何你认为值得注意的风格/方法错误。

然而,这个问题只涉及“快速放多”函数put()的内部循环。使用std::copy()变体编译,看起来一切正常,但是使用我自己的插入版本(-DBUGGY),数据部分地被破坏了。

代码语言:javascript
复制
#include <cstddef>
#include <array>
#include <vector>
#include <atomic>
#include <algorithm>
#include <type_traits>
#include <iterator>
#include <iostream>
#include <string>
#include <queue>
#include <chrono>



struct SerialBuffer
{
  std::array<char,127> fifo{};
  std::atomic<int8_t> hd = 0, tl = 0, vtl = 0;

  int8_t space(void) // return free space in queue
  {
    volatile int8_t tmp = hd - vtl - 1;
    if (tmp < 0) { tmp += 127; }
    return tmp;
  }

  int8_t reserve(int8_t n) // move virtual tail at once, reserving a run of bytes at end
  {
    volatile int8_t new_vtl = vtl;
    if (n <= space()) {
      if (new_vtl - 127 + n >= 0) { vtl = new_vtl - 127 + n; }
      else { vtl = new_vtl + n; }
      return new_vtl;
    }
    return -1;
  }

  int8_t next(int8_t i) // advance index in queue
  {
    if (i >= 127 - 1) { return 0; }
    return i + 1;
  }

  void confirm(void) // confirm the formerly reserved bytes as present in queue
  {
    tl = static_cast<int8_t>(vtl);
  }
  
  int8_t headroom(int8_t i) // return number bytes from queue index to queue end
  {
    return 127 - i;
  }
  
  template<typename iter_t>
  bool put(iter_t it, int8_t n) // (source, number of bytes)
  {
    int8_t i = reserve(n);

    if (i >= 0) {
      int8_t j = std::min(n, headroom(i)); // maybe two consecutive insert-ranges: first from i to buffer end, rest from buffer start

#ifdef BUGGY
      for (; i < 127; i++) {
        fifo[i] = *it++;
      }
      for (i = 0; i < n-j; i++) {
        fifo[i] = *it++;
      }
#else
      std::copy(it, it+j, fifo.begin()+i);
      std::copy(it+j, it+n, fifo.begin());
#endif
      
      confirm(); 
      return true;
    }
    return false;
  }
    
  bool put(std::vector<char> v) { return put(v.cbegin(),v.size()); }
  bool put(std::basic_string<char> v) { return put(v.cbegin(),v.size()); }

  
  void dump(int8_t k = 127)
  {
    if (space() < k) { hd = static_cast<int8_t>(tl); }
    else { hd = (hd + k) % 127; }
  }
  
  void print(void)
  {
    std::cout << "Head:" << (0+hd) << " Tail:" << (0+tl) << " VirtTail:" << (0+vtl) << std::endl;
    for (int8_t i = hd; i != tl; i = next(i)) { std::cout << fifo[i]; }
    std::cout << std::endl;
  }
};


int main(void)
{
  using namespace std::string_literals;
  
  SerialBuffer fifo1;
  auto tmp{"/uwb/x1/raw:123456789"s};
  std::vector<char> x(tmp.cbegin(),tmp.cend());

  std::queue<char,std::array<char,127>> fifo2;

  for (auto _: {1,2,3}) {
    for (int i=0; i < 10'000'000; i++) {
      if (!fifo1.put(x)) fifo1.dump();
    }
    fifo1.print();   
  }
} 

结果:

代码语言:javascript
复制
$ g++ bug.cpp --std=c++17 -O3 && ./a.exe
Head:52 Tail:115 VirtTail:115
/uwb/x1/raw:123456789/uwb/x1/raw:123456789/uwb/x1/raw:123456789
Head:104 Tail:103 VirtTail:103
/uwb/x1/raw:123456789/uwb/x1/raw:123456789/uwb/x1/raw:123456789/uwb/x1/raw:123456789/uwb/x1/raw:123456789/uwb/x1/raw:123456789
Head:28 Tail:70 VirtTail:70
/uwb/x1/raw:123456789/uwb/x1/raw:123456789

$ g++ bug.cpp --std=c++17 -O3 -DBUGGY && ./a.exe
Head:52 Tail:115 VirtTail:115
/uwb/x1/raw:123456789/uwb/x1/raw:123456789/uwb/x1/raw:123456789
Head:104 Tail:103 VirtTail:103
▒ե▒qс▒▒1▒3▒▒wb/x1/raw:123456789/uwb/x1/raw:123456789/uwb/x1/raw:123456789/uwb/x1/raw:123456789/uwb/x1/raw:123456789
Head:28 Tail:70 VirtTail:70
/uwb/x1/raw:123456789/uwb/x1/raw:123456789

正如您所看到的,在第二次运行中有乱码字节。我不明白我在那些看似无害的for循环中的错误在哪里。

编辑:正如@yzt指出的,这是一个令人尴尬的简单逻辑错误。我写了一个(正确的)第一个基于for的版本,然后改成了std::copy,然后在晚上太晚了,试图通过重写for循环来测量运行时差异,这一次错了。很抱歉,这是“不提交并在不运行时回家”错误的派生。正确的代码:

代码语言:javascript
复制
  n -= j;
  for (; j > 0; j--,i++) {
    fifo[i] = *it++;
  }
  for (i = 0; i < n; i++) {
    fifo[i] = *it++;
  }
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-10-07 09:01:53

一个错误是,在第一次循环中,当您从0开始(甚至在第一次调用时都会发生)时,您会将向量迭代器递增127次,这显然超出了范围。

副本可以工作,而循环不能工作,因为循环和正在进行的std::copy调用做的事情是不同的。它们不会在相同的索引处开始或停止,因此它们不会复制相同的内容。

与副本类似的循环如下:

代码语言:javascript
复制
for (ptrdiff_t index = 0; index < j; ++index)
    *(fifo.begin() + i + index) = *(it + index); // the exact way you dereference isn't important; could have used array subscripting
for (ptrdiff_t index = 0; index < n - j; ++index)
    *(fifo.begin() + index) = *(it + j + index);

此外,我建议您使用比int8_t更大的类型(例如int)来进行内部中间计算。可能有一些溢出或错误计算潜伏在那里。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69477619

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档