0

I'm working on a school assignment. I am writing a program that utilizes unions to convert a given IP address in "192.168.1.10" format into its 32-bit single value, two 16-bit values, and four 8-bit values.

I'm having trouble with implementing my structs and unions appropriately, and am looking for insight on the subject. To my understanding, unions point to the same location as the referenced struct, but can look at specified pieces.

Any examples showing how a struct with four 8-bit values and a union can be used together would help. Also, any articles or books that might help me would also be appreciated.

Below is the assignment outline:

Create a program that manages an IP address. Allow the user to enter the IP address as four 8 bit unsigned integer values (just use 4 sequential CIN statements). The program should output the IP address upon the users request as any of the following. As a single 32 bit unsigned integer value, or as four 8 bit unsigned integer values, or as 32 individual bit values which can be requested as a single bit by the user (by entering an integer 0 to 31). Or as all 32 bits assigned into 2 variable sized groups (host group and network group) and outputted as 2 unsigned integer values from 1 bit to 31 bits each.

I was going to cin to int pt1,pt2,pt3,pt4 and assign them to the IP_Adress.pt1, .... etc.

struct IP_Adress {
    unsigned int pt1 : 8;
    unsigned int pt2 : 8;
    unsigned int pt3 : 8;
    unsigned int pt4 : 8;
};

I have not gotten anything to work appropriately yet. I think I am lacking a true understanding of the implementation of unions.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 4
    [Union declaration](https://en.cppreference.com/w/cpp/language/union) _"...It is undefined behavior to read from the member of the union that wasn't most recently written...."_ – Richard Critten Nov 14 '22 at 22:40
  • Note: Any object can be viewed as an array of bytes, but the reverse is not true. If you don't care about the reverse, you can just store an `int` and cast the sucker when you need octets. – user4581301 Nov 14 '22 at 22:41
  • 3
    Unions won't help with this. In fact given that the requirement appears to cover CIDR ("assigned into 2 variable sized groups (host group and network group) from 1 bit to 31 bits each."), they'll actively get in your way. Just **learn the bitshift operators `<<` and `>>`**. – Ben Voigt Nov 14 '22 at 22:41
  • 1
    I would have looked into bitshift operations but the assignment requires the use of unions. I also am aware that the use of unions for conversion is not reccomended, but again, I am being forced to solve it in this way for the assignment. – PrometheusAurelius Nov 14 '22 at 22:44
  • 3
    Side note: Worth asking the instructor what endian they need. It could be important, and if not the blank look you get sometimes is hilarious. – user4581301 Nov 14 '22 at 22:45
  • ^lol, My goal is to get it working on my computer and deal with it later. (mine is little) – PrometheusAurelius Nov 14 '22 at 22:48
  • 2
    Yeah. Do what you need to do to pass the course and progress. It sounds like you already know that it will blow up in your face sooner or later if you're not really careful. – user4581301 Nov 14 '22 at 22:50
  • I am not sure why a `union` would be useful here as it would cause endian problems on some archtectures. – Galik Nov 14 '22 at 23:04

3 Answers3

2

A union is not a good fit for this assignment. In fact, nothing in the text you quoted even says to use a union at all. And, a union will not help you with the parts of the assignment that deal with "32 individual bit values" or with "32 bits assigned into 2 variable sized groups". Those parts of the assignment will require bit shifting instead. Bit shifting is the better way to solve the other parts of the assignment, as well.

That being said, if you absolutely must use a union, you are probably looking for something more like this instead:

union IP_Adress {
    uint8_t u8[4];   // four 8-bit values
    uint16_t u16[2]; // two 16-bit values
    uint32_t u32;    // one 32-bit value
};

Except that C++ does not allow you to write into one union field and then read from another. C allows that kind of type puning, but it is undefined behavior in C++.

Why is type punning considered UB?

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks for the reply. I only copied a small portion of the lab, but the lab it literally called "Union lab" and has a long portion on how he used a union to edit the first half of a 16bit value. So we HAVE to use unions to solve it. – PrometheusAurelius Nov 15 '22 at 01:08
  • 1
    @PrometheusAurelius Then you likely can't **legally** solve the problem in C++, only in C, because `union` can't be used in C++ for splitting and combining values the way you are obviously thinking of. Makes me think the teacher doesn't really understand what he's teaching. – Remy Lebeau Nov 15 '22 at 01:44
2

The asker already knows that doing this can blow up in their face a number of different ways, but here's a simple example for 4 byte, 4x1 byte, and 32x1 bit.

union bad_idea
{
    uint32_t ints; // 32 bit unsigned integer
    uint8_t bytes[sizeof(uint32_t)]; // 4 8 bit unsigned integers
};

and then

uint32_t get_int(const bad_idea & in)
{
    return in.ints;
} 
uint8_t get_byte(const bad_idea & in,
                size_t offset)
{
    if (offset >= sizeof(uint32_t)) // trap typos and idiots
    {
        throw std::runtime_error("invalid offset");
    }
    return in.bytes[offset];
} 
bool get_bit(const bad_idea & in,
                size_t offset)
{
    if (offset >= sizeof(uint32_t)*8)
    {
        throw std::runtime_error("invalid offset");
    }
    return (in.ints >> offset) & 1; // shift the required bit to the end (in.ints >> offset)
                                    // then mask off all of the other bits (& 1)
} 

Things get a bit ugly getting input because you can't simply

std::cin >> bad.bytes[0];

because it reads a single character. Type in 127 for the first octet and you'll wind up filling bad.bytes[0] through bad.bytes[2] with '1', '2', and '7'.

You need to involve a temporary variable

int temp;
std::cin >> temp;
// tests for valid range in temp
bad.bytes[0] = temp

or risk some explosive batsmurf like

std::cin >> *(int*)&bad.bytes[0];
// tests for valid value in bad.bytes[0] impossible because aliasing has been broken 
// and everything is already <expletive deleted>ed

pardon my C. The more respectable

std::cin >> *reinterpret_cast<int*>(&bad.bytes[0]);

isn't any better. As ugly as it is, use the temporary variable and bundle it up in a function to eliminate the duplication. Frankly this is a time when I'd probably fall back into C and pull out good ol' scanf.

user4581301
  • 33,082
  • 7
  • 33
  • 54
0

The assignment doesn't say c++, you can just use typecasting instead of a union. I like to print the 32bit address out in hex also as it's easier to make sure you have right 32bit value;

#define word8 uint8_t
#define word16 uint16_t
#define word32 uint32_t

char *sIP = "192.168.0.11";

main(){
    word32  ip, *pIP;

    pIP = &ip;
    inet_pton(AF_INET, sIP, pIP);
    printf("32bit:%u %x\n", *pIP, *pIP);
    printf("16bit:%u %u\n", *(word16*)pIP, *(((word16*)pIP)+1));
    printf("8bit:%u %u %u %u\n", *(word8*)pIP, *(((word8*)pIP)+1),*(((word8*)pIP)+2),*(((word8*)pIP)+3));
}

Output:

32bit:184592576 b00a8c0
16bit:43200 2816
8bit:192 168 0 11

You could also store the IP as a 4 byte string and do math to get the 16 bit and 32 bit answers. Its a pretty dumb assignment IMO; I would never use a union to do it.

Danial
  • 1,496
  • 14
  • 15