Search code examples
c++templatesstructunionsbit-fields

Experimenting with Unions and Bitfields within a structures and templates


To get a better handle and understanding of how bitfields, unions, and the byte alignment of structures work, I'm simulating a template Register structure.

The requirements of my Register are as follows:

  • Default size or width of a register is 8bits or 1 byte
  • Larger size registers must be a multiple of 8
  • Registers are less than or equal 64 bits in size or 8 bytes.

I have a set of structures which build off of each other in a cascading effect starting from the base unit of a Byte down to a QWord.

My Registers are template specializations.

Here is my code so far:

-main.cpp-

#include <iostream>
#include "Register.h"

int main() {
    Register r1;
    r1.value.value_ = 8;

    Register<16> r2;
    r2.value.value_ = 16;

    Register<32> r3;
    r3.value.value_ = 32;

    Register<64> r4;
    r4.value.value_ = 64;

    std::cout << static_cast<std::uint8_t>( r1.value.value_) << "\n";
    std::cout << static_cast<std::uint16_t>(r2.value.value_) << "\n";
    std::cout << static_cast<std::uint32_t>(r3.value.value_) << "\n";
    std::cout << static_cast<std::uint64_t>(r4.value.value_) << "\n";

    return EXIT_SUCCESS;
}

-Register.h-

#pragma once

#include <vector> // include for typedefs below.
typedef std::int8_t  i8;
typedef std::int16_t i16;
typedef std::int32_t i32;
typedef std::int64_t i64;

struct MyByte {
    union {
        i8 value_;
        struct {
            i8 b0 : 1;
            i8 b1 : 1;
            i8 b2 : 1;
            i8 b3 : 1;
            i8 b4 : 1;
            i8 b5 : 1;
            i8 b6 : 1;
            i8 b7 : 1;
        };
    };
};

struct MyWord {        // same as short or i16  
    union {
        i16 value_;
        union {
            MyByte byte_[2];
            struct {
                MyByte b0_;
                MyByte b1_;
            };
        };
    };
};

struct MyDWord {       // same as int or i32
    union {
        i32 value_;

        struct {
            MyWord w0_;
            MyWord w1_;
        };

        union {
            MyByte byte_[4];
            struct {
                MyByte b0_;
                MyByte b1_;
                MyByte b2_;
                MyByte b3_;
            };
        };
    };
};

struct MyQWord {     // same as long or i64
    union {
        i64 value_;
        struct {
            MyDWord d0_;
            MyDWord d1_;
        };
        struct {
            MyWord w0_;
            MyWord w1_;
            MyWord w2_;
            MyWord w3_;
        };
        union { 
            MyByte byte_[8];
            struct {
                MyByte b0_;
                MyByte b1_;
                MyByte b2_;
                MyByte b3_;
                MyByte b4_;
                MyByte b5_;
                MyByte b6_;
                MyByte b7_;
            };
        };
    };
};

template<size_t N = 8>
struct Register {
    MyByte value;
    Register() {
        static_assert(
         ((N % 8) == 0) &&
         (N >= 8) &&
         (N <= 64)

        );
    }
};

template<>
struct Register<16> {
    MyWord value;
    Register() = default;
};

template<>
struct Register<32> {
    MyDWord value;
    Register() = default;
};

template<>
struct Register<64> {
    MyQWord value;
    Register() = default;
};

The above code compiles, runs and exits with a code of 0 in Visual Studio 2017 with the compiler set to the latest draft standard.

Now that you have seen the code, I understand bitfields and unions to some degree but when I don't use them all that often they can trip me up a bit. I do know that when using them especially in a combined manner that it can cause the code to not be portable especially across different compilers, operating systems and architectures (endian).

However this is just experimental code to practice on them as a good refresher.

The issue I'm having here is that of my output. All of the higher order registers seem to be working okay, I have only tested getting it through the inner most member variable value_ directly. However, I am getting results from the default or most basic register the 8 bit sized one. With the values being set as they currently are. I'm getting this as output:

--

16
32
64

And if I change my main to this:

#include <iostream>
#include "Register.h"

int main() {
    Register r;
    for (i8 i = 0; i < 21; i++) {
        1.value.value_ = i;
        std::cout << static_cast<std::uint8_t>(r.value.value_) << "\n";
    }
    return EXIT_SUCCESS;
}

I am getting this output with a beep some where in the mix of it:

☺
☻
♥
♦
♣
♠





♂
♀

♫
☼
►
◄
↕
‼
¶

Does this have something to do with how std::int8_t is defined? Is it based on a char value type instead of a int type? It should still be integral though... if this is the case than does it have to deal with unsigned values within unions or bitfields etc.? What is causing the ASCII symbols to print to the console.


Solution

  • Is it based on a char value type instead of a int type?

    char *is* an integer type. The standard allows std::int8_t to be a typedef to char.

    std::cout << static_cast<std::uint64_t>(r.value.value_) << "\n";
    

    ftw.