6
\$\begingroup\$

For a few months now I have been working with an ARM CPU. To be specific an ARM Cortex M3 from STM (STM32107VC). Complete example

So far I am using the StdPeriphal library, and will continue to do so but I came across some oddities which led me to write the following class to represent register in more C++ way rather then calling library functions or querying registers directly. This bit shifting or masking i just do not like it.

So I came up with the idea of bitfields. Bitfields are already part of C++/C so that you can specify how many bits of a certain integer data type shall be used. (see)

But this did not satisfy my needs because at compile time I lose the information of how many bits certain parts of a struct will have and they do not map directly onto ONE memory location.

Unions on the hand do exactly that. Every data member of a union is mapped onto one location in memory which has the size of the biggest possible data member so that you can look at the underlying memory with different byte interpretations (a.k.a. the different datatypes of the unions member).

The first requirement was to define a new way for Bitfields which do not lose the information about how many bits they are describing and where they start within a memory location. This leads automatically to a template design:

template <typename T, size_t Index, size_t Bits = 1, T Mask = ((1u << Bits) - 1u)>
class BitField {
private:
    T mValue;
public:
    BitField &operator=(T value) {
        mValue = (mValue & ~(Mask >> Index)) | ((mValue & Mask) << Index);
        return *this;
    }

    size_t Start() { return Index; }
    size_t End() { return Index + Bits; }

    operator T() const { return (mValue >> Index) & Mask; }
    explicit operator bool() const { return mValue & (Mask << Index); }

};

With this I have new way of describing bitfields within an byte/word or what ever I want to use as bit 'range'. The next step was to actually describe a Register. For this example I will hold that register totally generic.

union Register
{
    struct Part1 {
        typedef BitField<uint32_t, 0, 8>    Bits;
    };
    struct Part2 {
        typedef BitField<uint32_t, 8,8>   Bits;
    };
    struct Part3 {
        typedef BitField<uint32_t, 16, 8>   Bits;
    };
    struct Part4 {
        typedef BitField<uint32_t, 24, 8>   Bits;
    };

    union Bit
    {
        Register::Part1::Bits Part1;
        Register::Part2::Bits Part2;
        Register::Part3::Bits Part3;
        Register::Part4::Bits Part4;
    } field;

    uint32_t rawValue;

    Register(uint32_t value) : rawValue{ value } {}
};

This union first describes the different parts for a certain theoretical register. Each struct within the union just holds an typedef for certain Bitfield with in our RawValue. The name of those structs should later of course correspond with the specification of an actual register.

The inner union then holds actual "instances" of the different typedefs which were previously defined. Through this we are capable to just ask for certain part of our rawValue.

int main(int , char**) {

uint32_t DATA = 0x1234'5678;
Register* Register = reinterpret_cast<::Register*>(&DATA);

std::cout << std::hex << "Raw Value: \t-> 0x" << Register->rawValue << std::dec << "\n";

std::cout << "Part1 " << Register->field.Part1.Start() << "-" << Register->field.Part1.End() << " \t-> 0x" << std::hex << Register->field.Part1 << std::dec << "\n";
std::cout << "Part2 " << Register->field.Part2.Start() << "-" << Register->field.Part2.End() << " \t-> 0x" << std::hex << Register->field.Part2 << std::dec << "\n";
std::cout << "Part3 " << Register->field.Part3.Start() << "-" << Register->field.Part3.End() << " \t-> 0x" << std::hex << Register->field.Part3 << std::dec << "\n";
std::cout << "Part4 " << Register->field.Part4.Start() << "-" << Register->field.Part4.End() << " \t-> 0x" << std::hex << Register->field.Part4 << std::dec << "\n";

    return 0;
}

Complete example

I hope you like that code snippet. Any criticism is welcome :D

\$\endgroup\$
  • 3
    \$\begingroup\$ Your code snippet contains undefined behaviour since you access different union members. (To non C++-users, that doesn't make the code "broken", so no need to close as off-topic. It can work according to the users specification with a compiler, but the C++ standard does not guarantee that behaviour, thus undefined behaviour) \$\endgroup\$ – Zeta Jan 1 '18 at 15:24
  • \$\begingroup\$ I wasn't aware of that fact thanks for letting me know. \$\endgroup\$ – ExOfDe Jan 1 '18 at 15:26
  • 2
    \$\begingroup\$ That's why you usually see unions used as struct { enum { ... } which_type; union { ... } data; };. That problem is solved by C++17's variant, fortunately. \$\endgroup\$ – Zeta Jan 1 '18 at 15:34
  • \$\begingroup\$ Then i will try to create a version with std::variant. I'm curious if this is going as smooth as this not quite correct example :D \$\endgroup\$ – ExOfDe Jan 1 '18 at 15:51
  • 1
    \$\begingroup\$ @200_success you're missing a "not" in your last sentence, unless you want to fundamentally change the role of comments :P. Either way, I didn't have time for a complete CR yesterday,. \$\endgroup\$ – Zeta Jan 2 '18 at 7:10
5
\$\begingroup\$

A C++ developer's least favorite behaviour — Undefined

Your code contains undefined behaviour. That's not easy to spot, though, since unions like this can yield defined behaviour. In the following example,

union Example {
    struct { int value; double custom; } double_field;
    struct { int value; int    custom; } int_field;
    struct { int value; char   custom; } char_field;
};

all structs share a common initial part of the members, namely the value, so as long as Example ex has been properly initialized, we can use ex.char_field.value or ex.char_field.value to inspect (not change) the value. In order to update a field, we need to use the active member, e.g.

Example ex;
ex.double_field = {... , ...};

cout << ex.double_field.value; // ok, double_field is active

cout << ex.char_field.value;   // ok, value is part of common initial sequence
cout << ex.char_field.custom;  // undefined behaviour, char_field is not active
                               // and custom is not part of the common initial sequence

ex.char_field.custom = 'c';    // undefined behaviour, char_field is not active
ex.char_field.value  = 10;     // undefined behaviour, char_filed is not active

ex.char_field = { ... };
ex.char_field.custom = 'c';    // ok, char_field is active
ex.char_field.value  = 10;     // ok, char_field is active

So if all your (standard-layout) Bit structures were only involved, we would actually have defined behaviour. But there's the raw_value, and that's not guaranteed to be even memory compatible to the almost trivial BitField struct. Yes, even struct { T m ; }; and T m aren't guaranteed to be memory compatible.

Even if they were memory compatible, the standard doesn't describe the behaviour of

template <typeanem T> 
union U { T value; struct Wrapper { T wrapped; }};

which is essentially what you've tried, at least as far as I know.

The problem is none

So let's get back to the drawing board. We have our register value uint32_t register_value, and we would like to extract certain bits, let's say the first five bits after an offset of 3 (3-7; inclusive), which in our case encode the number of active lines in a system (0-15). We have several possible solutions now, all with their advantages and disadvantages.

For example, you could write

template <typename Bittable>
constexpr Bittable extract_bits(Bittable v, unsigned offset, unsigned bits) {
    // assert is not constexpr, but you could introduce it here
    // assert(offset + bits <= number_of_bits_in(v));
    return (v >> offset) & ((1u << bits) - 1u);
}

or

template <typename Bittable, unsigned int Offset, unsigned int Bits>
constexpr Bittable extract_bits_safe(Bittable v, unsigned offset, unsigned bits) {
    static_assert(number_of_bits_in<Bittable>::value >= Offset + Bits,
                  "Not enough bits to support operation");
    return extract_bits(v, Offset, Bits);
}

or

template <typename Bittable, unsigned int Offset, unsigned int Bits>
class BitAccess{
    static constexpr Bittable access(Bittable v) {
        return extract_bits_safe<Bittable,Offset,Bits>(v);
    }
    static constexpr Bittable operator()(Bittable v) {
        return access(v);
    }
    static constexpr unsigned int offset = Offset;
    static constexpr unsigned int bits   = Bits;
}

and they will all look similar:

static constexpr unsigned LINES_OFFSET = 3;
static constexpr unsigned LINES_BITS = 5;

lines = extract_bits(register_value, LINES_OFFSET, LINES_BITS);
lines = extract_bits_safe<uint32_t, LINES_OFFSET, LINES_BITS>(register_value);
lines = BitAccess<uint32_t, LINES_OFFSET, LINES_BITS>::access(register_value);

The last one is almost the same as your variant, but it doesn't contain any non-static members anymore.

Either way, a guaranteed defined behaviour solution depends on your use case.

\$\endgroup\$
  • \$\begingroup\$ Thanks alot for your effort here. To bad that in c++ type punning is undefined behaviour compared to c where it is defined see Defect Report #283 which adds an Technical Corrigendum to the c standard. \$\endgroup\$ – ExOfDe Jan 6 '18 at 0:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.