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;
}
I hope you like that code snippet. Any criticism is welcome :D
1 Answer 1
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.
-
\$\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– ExOfDe2018年01月06日 00:52:22 +00:00Commented Jan 6, 2018 at 0:52
Explore related questions
See similar questions with these tags.
struct { enum { ... } which_type; union { ... } data; };
. That problem is solved by C++17's variant, fortunately. \$\endgroup\$