2
\$\begingroup\$

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 8 bits or 1 byte
  • Larger size registers must be a multiple of 8
  • Registers are in the range of 8 <= N <= 64 bits in size.

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.

main.cpp

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

int main() {

    Register r1;
    std::cout << "Register<8>\n";
    for (i8 i = 0; i < 21; i++) {
        r1.register_.value_ = i;
        // make sure to cast to a larger size due to int8_t being defined by char.
        std::cout << static_cast<std::uint16_t>(r1.register_.value_) << "\n";
    }
    std::cout << '\n';

    // Note: my output shows: 0 1 1 0 0 1 0 0 
    // as I am running on an intel x86-64 bit Quad Core Extreme
    // this is expected since my machine is little endian.
    r1.register_.value_ = static_cast<std::int8_t>( 38 );
    std::cout << "Bit Values\n";
    std::cout << static_cast<bool>(r1.register_.b0) << " " 
              << static_cast<bool>(r1.register_.b1) << " " 
              << static_cast<bool>(r1.register_.b2) << " " 
              << static_cast<bool>(r1.register_.b3) << " "
              << static_cast<bool>(r1.register_.b4) << " " 
              << static_cast<bool>(r1.register_.b5) << " " 
              << static_cast<bool>(r1.register_.b6) << " " 
              << static_cast<bool>(r1.register_.b7) << "\n\n";

    Register<16> r2;
    std::cout << "Register<16>\n";
    for (i16 i = 0; i < 21; i++) {
        r2.register_.value_ = i;
        // make sure to cast to a larger size due to int8_t being defined by char.
        std::cout << static_cast<std::uint16_t>(r2.register_.value_) << "\n";
    }
    std::cout << '\n';

    Register<32> r3;
    std::cout << "Register<32>\n";
    for (i32 i = 0; i < 21; i++) {
        r3.register_.value_ = i;
        // make sure to cast to a larger size due to int8_t being defined by char.
        std::cout << static_cast<std::uint32_t>(r3.register_.value_) << "\n";
    }
    std::cout << '\n';

    Register<64> r4;
    std::cout << "Register<64>\n";
    for (i64 i = 0; i < 21; i++) {
        r4.register_.value_ = i;
        // make sure to cast to a larger size due to int8_t being defined by char.
        std::cout << static_cast<std::uint64_t>(r4.register_.value_) << "\n";
    }
    std::cout << '\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 register_;
    Register() {
        static_assert(
         ((N % 8) == 0) &&
         (N >= 8) &&
         (N <= 64)

        );
    }
};

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

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

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

Output

Register<8>
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

Bit Values
0 1 1 0 0 1 0 0

Register<16>
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

Register<32>
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

Register<64>
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

I had asked a question on Stack Overflow about specific output due to behavior of std::int8_t being defined from char. That is now fixed.

I have a general idea or grasp on unions, bitfields, byte alignment of structs etc. and I am aware of the complications of bitfields especially when it comes to the Endian-ness of a machine.

After my question was answered I had dropped a basic general question in the comment of that answer and my question there was this:

"Do you see any other issues or major concerns with how the bitfield and nameless unions and structs are coupled together?"

I was giving a link to this talk as a reply to my question in which I had watched. It has given me some insight and useful information which now leads me to here with a handful of questions and concerns about my code. Some pertain to the video while others don't. Before I start to ask my questions or list my concerns, there are a few things about my code or the particular behavior that I am after.

Since I'm simulating what a memory register in a PC would be like I'm trying to model the fact that I had mentioned above about the default and basic unit size is 8 bytes, that a register can be a multiple of 8 and that it has to be in the range of [8,64]. I also like the idea that I would have an 8 bit value at the base level but coupled together with a union and a bitfield only to be able to quickly and easily access the individual bits. And as the size of the registers get larger, the same cascading effect should apply. For example in a 64-bit register the actual single value_ is 64 bits or 8 bytes wide in memory, but is coupled with unions to smaller sizes so that one could access a specific DWord, Word or Byte from within a QWord and they can access any one of its 64 bits. This is the behavior of the structure that I am after since I'm trying to model a register in which I would like to use in one of my projects where I'll be emulating a virtual PC. Now that you have the background. I'll go ahead and get to the questions and concerns.

Questions and concerns

  • I would like to know what your thoughts and opinions are on the overall structure of my code and its design as is without any influence of my questions and concerns below. Meaning, these replies would be completely dependent on the code itself and independent of any other proposal, another words unbiased.
  • Is there a more simplified and compact way to do this without having to worry about type casting?
  • Since this is a personal project and I'm not overly concerned with the Endian, however I do feel that it should be addressed early on as opposed to later on if I decide to make this portable, what could be done to minimize or alleviate the checks and conversions due to Endian?
  • I would like to have these registers to be trivially copyable. Meaning you can copy one Register<8> to another Register<8> but you wouldn't be able to copy a Register<16> to a Register<32>. I was thinking of maybe making a function that would allow you to copy from one Register type to another.
  • Would using std::bitfield<> and the use of bitstreams be a viable option?
    • If so how would I be able to incorporate them into this design?
    • If not what is the main cause or reasoning behind why they wouldn't work? Would this be considered Type Punning and if so, would this be a Good or Bad example; please explain either case along with their pros and cons.
  • There is only one nuisance to me right now and that is the fact that I have to go an extra level deep to retrieve my values for example:

    Register r;
    r.register_.value_ = 12;
    

NOTE: -- My code does not have to be 100% strictly enforced by the standard as I don't have a recent copy of the C++17 standard and only sections of outdated C++11 versions of the standard. However it should at the least be minimally compliant.

\$\endgroup\$

Your Answer

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

Browse other questions tagged or ask your own question.