0

I am trying to write bit fields structure to file and then read it.

For example:

typedef struct{
     ushort 
              a:4, 
              b:4, 
              c:4, 
              d:4;
} teststruct;

I try to write and read it like this

QDataStream &operator <<(QDataStream &st, const teststruct &a)
{
    st <<a.a << a.b << a.c << a.d;
    return st;
}

QDataStream &operator >>(QDataStream &st, teststruct &a)
{
    st >>a.a >> a.b >> a.c >> a.d;
    return st;
}

teststruct str1, str2;   
str1.a = 1;
str1.b = 0;
str1.c = 1;
str1.d = 0;

QFile f("testfile");
f.open(QFile::WriteOnly);
QDataStream st(&f);

st << str1;
f.close();
f.open(QFile::ReadOnly);
QDataStream st(&f);   
st >> str2;
f.close();

But in QDataStream::operator>> I got an error

error: cannot bind bitfield 'a.teststruct::a' to 'quint16& {aka short unsigned int&}'

What can I do with >> operator or maybe there is other way to read data to my structure?

Rohan Khude
  • 4,455
  • 5
  • 49
  • 47
  • You cannot have a non-const reference to a bit-field. `QDataStream::operator>>(quint16 &i)` takes the parameter as a non-const reference (as the error message you get shows), that is why you get the error. – thuga Dec 02 '16 at 14:24

2 Answers2

1

In your example you should notice that the data saved to file is probably incorrect. So for example, having the following struct:

struct BitStruct
{
    uint8_t     b1:4;
    uint8_t     b2:4;
};

and the operator written as:

QDataStream &operator <<(QDataStream &st, const BitStruct &a)
{
    st <<a.b1 << a.b2;
    return st;
}

when you write sample data BitStruct bits{0x1, 0x2}; to file you will have 2 bytes written. Binary contents of the file would be 0x01 0x02 which is probably not what you want to achieve.

It happens due to the fact that calling st << a.b1 results in the b1 field being converted to one of the types handled by QDataStream which in this case is most probably quint8 (you can read more in docs).

To fix this behaviour you can modify the QDataStream::operator<< implementation to:

st.writeRawData(reinterpret_cast<const char*>(&a), sizeof(BitStruct));

On the other hand, to read the data to such structure you should do a similar update in the QDataStream::operator>> implementation:

st.readRawData(reinterpret_cast<char*>(&a), sizeof(BitStruct));

This would allow to write the structure in a compact manner as intended and read particular bit fields accordingly.

This way you have your whole structure written/read in a single approach and you don't have to worry about further growth of the structure (additional fields) and updating both operator implementations.

Dusteh
  • 1,496
  • 16
  • 21
0

I presume that the reason you have your bitfield structure is so that its size is that of ushort (really uint16_t), and passing it around by value is cheap and it takes the minimum space possible. That's an OK reason, so let's go with it.

Do note that the structure's in-memory layout doesn't have anything to do with its on-disk layout! The on-disk layout is determined by how you use the QDataStream and its operators. The disk layout as you show it wastes 75% of space - each value takes 16 bits, but it only needs 4:

(uint16_t a) (uint16_t b) (uint16_t c) (uint16_t d)

The key to fix it is to use intermediate values as an interface between the structure and the data stream.

Thus:

QDataStream &operator <<(QDataStream &st, const teststruct &a)
{
    uint8_t v0 = (a.d << 4) | a.c;
    uint8_t v1 = (a.b << 4) | a.a;
    st << v0 << v1;
    return st;
}

QDataStream &operator >>(QDataStream &st, teststruct &a)
{
    uint8_t v0, v1;
    st >> v0 >> v1;
    a.a = v1;
    a.b = v1>>4;
    a.c = v0;
    a.d = v0>>4;
    return st;
}

The on-disk layout now wastes no space, and is as follows (using pseudotypes):

[(uint4_t d) (uint4_t c)] [(uint4_t b) (uint4_t a)]
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313