【问题标题】:Concatenating Bits in C++C++ 中的连接位
【发布时间】:2015-03-17 15:00:25
【问题描述】:

我正在开发一个用于卷积编码器和位穿孔的软件。

所以在我的程序中,每个循环都会生成一个 55 位长度的数据。它存储在 unsigned long long 类型变量的前 55 位中。每次迭代后,我必须将这 55 位传输到 unsigned char 的缓冲区中(缓冲区非常大,比如大约 500 字节)。这些位必须连续存储在此缓冲区中。 (即没有间隙)。像这样轻松连接有什么技巧吗?

【问题讨论】:

  • 你能改变你正在使用的数据类型吗?
  • 你能用std::bitset吗?
  • 我不知道任何简单的技巧。您必须要么使用某种位打包数据结构,要么组装位 oldskool 样式。
  • @adam10603 如果它有助于解决这个问题,我可以。
  • @NathanOliver bitset 的问题在于它的大小是固定的,并且不容易转换回长字节数组,除非我遗漏了什么。

标签: c++ bit-manipulation


【解决方案1】:

编辑:有人正确地向我指出,因为我通过 uint64_t* 对字符缓冲区进行别名处理,所以我违反了严格的别名规则,并且由于字节序。尽管如此,如果您对您的平台有一定的保证,这可能仍然有效。它可以通过使用 64 位元素的缓冲区而不是单个字节来解决。

如果你想要一个不依赖任何外部库的准系统算法,你可以使用下面的 sn-p。请注意,bitset 仅用于显示结果并确保其有效。

作为测试,我定义了一个 55 位模式,其中包含 54 个连续的 1,后跟一个 0。64 位值 (x) 中剩余的 9 位也是零。缓冲区是一个 500 字节的字符数组,我将其别名为 auint64_t*。该算法跟踪当前的 64 位块 (currentBlock) 和该块中的当前位 (currentBit)。它执行以下操作:

  1. 移动位模式,使其从当前位位置开始。
  2. 将结果与当前 64 位块进行或运算。这意味着位模式的第一部分连接到当前块的任何剩余部分。例如,在第二次迭代中,当第一个块的前 55 位被填充时,剩余的 9 位将被占用。
  3. 更新currentBit 变量。
  4. 检查currentBlock 是否溢出,如果溢出,则移动到下一个块并连接 55 位模式的其余部分。

    #include <iostream>
    #include <bitset> // only used for testing, not for the algorithm
    using namespace std;
    
    int main()
    {
        size_t nBits = 55;
    
        // 54 1's, followed by 10 0's
        uint64_t x = 0b1111111111111111111111111111111111111111111111111111110000000000;
    
        // 500 byte buffer:
        char buf8[500]{};
        uint64_t *buf = reinterpret_cast<uint64_t*>(&buf8[0]);
    
        // This would be better; use this if possible
        // uint64_t buf[100];
    
        int currentBit = 0;
        int currentBlock = 0;
    
        // concatenate the 55-bitpattern 10 times 
        for (int i = 0; i != 10; ++i)
        {
            buf[currentBlock] |= (x >> currentBit);
    
            currentBit += nBits;
            if (currentBit >= 64)
            {
                ++currentBlock;
                currentBit %= 64;
    
                buf[currentBlock] |= x << (nBits - currentBit);
            }
        }
    
        // TEST
        for (int i = 0; i != 5; ++i)
            cout << bitset<64>(buf[i]) << '\n';
    }
    

您可能应该对此进行概括并将其封装在一个函数中。这取决于你。程序原样产生以下输出:

1111111111111111111111111111111111111111111111111111110111111111
1111111111111111111111111111111111111111111110111111111111111111
1111111111111111111111111111111111110111111111111111111111111111
1111111111111111111111111110111111111111111111111111111111111111
1111111111111111110111111111111111111111111111111111111111111111

注意每 55 位的 0 标记。

【讨论】:

  • reinterpret_cast = 不好。适用于您的编译器,甚至可能适用于他的编译器。但不能保证。
  • @NeilKirk 如果我错了,请纠正我,但我相信标准中说允许将char* 转换为任何其他指针类型。编辑:我对通过char* 的别名感到困惑,而不是相反......所以我的代码不遵守严格的别名规则......该死的
  • 很多事情都是允许的,但并不总是按您期望的方式工作。存储在buf8 中的结果在小端机器和大端机器之间会有所不同。
  • 如果我们要讨论大端/小端,位操作从定义上来说是很棘手的......不过你可以检查并纠正这一点。
  • @Shreyes 不客气。您可以通过使用uint64_t 的数组使其可移植,在这种情况下您不再需要演员表。如果以后需要字节数组,可以强制转换为char*unsigned char*,而不会违反严格的别名。此外,当使用空大括号初始化器时,数组被初始化为零。但你是对的,这是 OR 相应工作所必需的。
【解决方案2】:

我不久前写了这个类,当时我需要处理比特。它也可能对您有用。这里是:

#include <deque>
#include <vector>
#include <algorithm>

class bitStream {
public:
    bitStream() {}

    /// Copies the data from another bitStream into this one upon construction
    bitStream(const bitStream& bStream, bool reverse=false) {
        this->appendData(bStream, reverse);
    }

    /// Copies the data from a vector of booleans upon construction
    bitStream(const vector<bool>& vec, bool reverse=false) {
        this->appendData(vec, reverse);
    }

    /// Appends data to the stream from a uint64_t type. The lower-n bits will be appended, starting with the highest bit of those by default.
    void        appendData(uint64_t data, size_t n, bool reverse=false) {
        deque<bool> _buffer;
        n = (n>64)?64:n;
        for (int i=0; i<n; i++) {
            _oneBit tmp;
            tmp.data = data;
            _buffer.push_back(tmp.data);
            data >>= 0x1;
        }
        if (!reverse) std::reverse(_buffer.begin(), _buffer.end());
        for (const auto v: _buffer) _data.push_back(v);
    }

    /// Appends data to the stream from a C-style array of booleans
    void        appendData(bool* data, size_t n, bool reverse=false) {
        if (reverse) {
            for (int i=0; i<n; i++) this->appendBit(*(data+(n-i-1)));
        } else {
            for (int i=0; i<n; i++) this->appendBit(*(data+i));
        }
    }

    /// Appends data to the stream from a vector of booleans
    void        appendData(const vector<bool>& vec, bool reverse=false) {
        if (reverse) {
            for (auto i = vec.size()-1; vec.size() > i; --i) this->appendBit(vec.at(i));
        } else {
            for (const auto& v : vec) this->appendBit(v);
        }
    }

    /// Appends a single bit
    void        appendBit(bool bit) {
        _data.push_back(bit);
    }

    /// Appends the bits from another bitStream object to this one
    void        appendData(const bitStream& bStream, bool reverse=false) {
        if (!bStream.getSize()) return;
        if (reverse) {
            for (int i=0; i<bStream.getSize(); i++) this->appendBit(*(bStream.getData()+(bStream.getSize()-i-1)));
        } else {
            for (int i=0; i<bStream.getSize(); i++) this->appendBit(*(bStream.getData()+i));
        }
    }

    /// Returns a pointer to the begining of the data (read-only!)
    const bool* getData() const { return &_data.front(); }

    /// Reference to the bit at a specified position (assignable, but at lest n+1 elements must exist before calling!)
    bool& operator[] (size_t n) {
        if (n>_data.size()-1) throw runtime_error("Out of range!");
        return _data.at(n);
    }

    /// Fills your vector with the data chopped up into "sizeof(T)"-byte pieces.
    template <typename T>
    void        getDataAsVector(vector<T>& vec) {
        vec.clear();
        size_t oSize = sizeof(T)*8;
        T tmp = 0x0;
        for (int i=0; i<_data.size(); i++) {
            if (!(i%oSize) && i) {
                vec.push_back(tmp);
                tmp = 0x0;
            }
            tmp <<= 0x1;
            tmp |= _data[i];
        }
        vec.push_back(tmp);
    }

    /// Returns the number of bits that are stored
    size_t      getSize() const { return _data.size(); }

    /// Reverses the stored bits
    void        reverse() { std::reverse(_data.begin(), _data.end()); }
private:
    deque<bool> _data;
    struct _oneBit {
        uint8_t data:1;
    };
};

使用起来相当简单,所以我没有费心写一个例子。

首先,我实际上从未在项目中使用过它,因此可能存在一些错误(我记得只对其进行了非常简短的测试),而且它还没有完全完成(有几个可能的构造函数我还没有实现)。此外,尽管名称为“流”,但它与 C++ 中的流无关。

所以基本上它可以让你存储位。您放入其中的位将存储在连续的内存地址(是的,这是每个内存字节一个位,但无论如何),在对象的生命周期内保持不变(这是因为 deque 不重新分配自己不像vector)!您可以通过各种方式(从不同的数据源,以反向或非反向顺序)向其附加位,并以不同的形式将它们读回。在您的情况下,appendData(uint64_t data, size_t n, bool reverse=false) 函数是您可以用来填充它的函数。要取回您的数据,您可以使用 vector&lt;char&gt; 填充数据,并将其切成 1 字节的片段(字符),如果您愿意的话!

再次重申,请不要将此视为 100% 经测试且有效的事情。我只是简单地测试了它,所以你应该尝试一下,看看它是否适合你!我想我会与你分享,以防你觉得它有帮助。

【讨论】:

  • 我以为他想将其存储为每位 1 位,而不是字节。我没有看到这个实现在任何地方都这样做......我忽略了什么吗?
  • 在内部它将它们存储在单独的字节中,是的。但是你可以要求它用一个个的位来填充任何类型的向量!因此,如果您愿意,您可以通过这种方式获取数据。在内部它不是这样存储的,但你为什么要关心呢?调用 getDataAsVector() 来填充 vector 或其他东西,它会将这些位“压缩”成字符,并根据需要用尽可能多的字符填充向量,然后就可以了。这几乎就是 OP 所要求的。
  • @adam10603 谢谢看看。
  • 嗯,我明白了。非常好,但它会导致相当多的开销,否则你不会有。由于需要生成压缩版本,在内存(8 倍的内存消耗,加上您在需要连续数组时返回的向量的大小)和性能方面都是如此。
  • 我知道,但是在我写这篇文章时的场景中,这些都不重要。我希望 OP 也不关心这些。
猜你喜欢
  • 2021-09-24
  • 2017-12-22
  • 2016-11-24
  • 1970-01-01
  • 1970-01-01
  • 2010-09-17
  • 1970-01-01
  • 2013-10-06
  • 1970-01-01
相关资源
最近更新 更多