C++

union & byteorder

C++ - union讲解 & 字节序讲解

Posted by Zhgaot on August 16, 2021

1 union基本概述

1.1 union定义

union意为联合,它是一种特殊的类,通过关键字union进行定义。一个union可以有多个数据成员,但其内部只能包含结构体(struct)和普通变量类型(如int等),并以其中一个占内存最大的变量的大小S就是该union所占内存的大小。例如以下联合体Token

union Token{
   char cval;
   int ival;
   double dval;
};

联合体Token的内存如下图所示:

在union被声明好后,union中的3个变量就对应着8个字节的内存,只要给其中一个赋值,其他变量就可以直接使用的,只要到他对应的内存位置取值就行。

1.2 互斥赋值

在任意时刻,联合中只能有一个数据成员可以有值。当给联合中某个成员赋值之后,该联合中的其它成员就变成未定义状态了。

Token token;  // (1)
token.cval = 'a';  // (2)
token.ival = 1;  // (3)
token.dval = 2.5;  // (4)

(1) 上述代码定义了联合Token的一个变量token,此时token所占内存的数据如下图所示,红色方框内的数据即为token所占内存数据。因为token中长度最大的变量是double类型,所以token的长度是8个字节:

(2) 之后首先为token的变量cval赋值,此时token所占内存的数据如下图所示,此时,token所占内存的第一个字节的值变为0x61,即字符’a’

(3) 接下来为token的变量ival赋值,此时token所占内存的数据如下图所示,此时,token所占内存的前四个字节变为0x00000001,即为数字1。在对tokenival赋值之后,cval的值就变为了0x01,实际上就没有意义了:

(4) 最后,为token的变量dval赋值,此时token所占内存的数据如下图所示,此时,token所占内存的八个字节都有了相应的值。在对tokendval赋值之后,cval的值变为了0x00,而ivale的值变为了0x00000000,都没有了实际意义,也就是之前提到的未定义状态

1.3 访问权限

union可以为其成员指定public、protected和private等访问权限,默认情况下,其成员的访问权限为public。例如,在“1.1 定义”中定义的联合Token,其三个成员的访问权限均为public。

2 为union成员指定长度

在“1.2 互斥赋值”中提到,联合的存储空间至少能够容纳其最大的数据成员。当然,也可以通过冒号操作符来为union的成员指定长度,如下述代码所示:

union U {
	unsigned short int aa;
	struct {
		unsigned int bb : 7;//(bit 0-6)
		unsigned int cc : 6;//(bit 7-12)
		unsigned int dd : 3;//(bit 13-15)
	}; 
} u;

以上代码定义了一个名为U的union,并且定义了U的变量u。unionU包含两个成员,一个是unsigned short int类型的变量,其大小为2个字节;另一个是一个自定义结构,该自定义结构中包含了3个unsigned int类型的变量。需要注意的是,每个unsigned int类型的变量的大小并不是默认的4个字节,而是通过冒号操作符指定了其大小,该大小的单位是比特。所以,联合u的大小是2个字节。

之后,对联合u中的aa成员进行赋值:

u.aa = 0xE07F;

此时,联合u所占的内存数据如下图所示:

此时,u.bb所处的位置是0-6比特,对应的值为127;u.cc所处的位置是7-12比特对应的值为0;u.dd所处的位置是13-15比特对应的值为7,如下图所示:

3 union中定义struct

如“1.1 定义”所述,union内部只能包含结构体(struct)和普通变量类型。

3.1 例一

#include <stdio.h>
union
{
    int i;
    char x[2];
}a;
 
void main()
{
    a.x[0] = 10;
    a.x[1] = 1;
    printf("a.i = %d\n", a.i);  // a.i = 266
}

分析:现代PC大多采用小端字节序,即高位高地址、低位低地址(详见“4”);由于上述a内部有int类型变量,因此a占用4字节空间,但char型2元素数组仅占用2字节空间;如上述,先向char型数组第0个元素赋值10,相当于赋值在int型变量的低地址位置,即0x0A,后向char型数组第1个元素赋值,相当于赋值在int型变量的高地址位置,即0x01,因此int型变量i当前为0x0000010A,如下图所示uniona的内存分布:

3.2 例二

#include <stdio.h>
void main()
{
    /*定义一个联合*/
    union {
        int i;
        /*在联合中定义一个结构*/
        struct {
            char first;
            char second;
        }half;
    }number;

    /*联合成员赋值*/
    number.i = 0x4241;  // 0x42=B, 0x41=A
    printf("%c %c\n", number.half.first, number.half.second);  // A B
    
    /*联合中结构成员赋值*/
    number.half.first = 'a';  // 'a'=0x61
    number.half.second = 'b'; // 'b'=0x62
    printf("%x\n", number.i); // 6261
}

4 byteorder

4.1 小端字节序和大端字节序

现代CPU的累加器一次能够装载4字节(这里考虑32位机),即一个整数。那么这4字节在内存中的排列顺序将影响它被累加器装载成的整数的值,这就是字节序问题。

字节序分为小端字节序(little endian)和大端字节序(big endian):小端字节序是指一个整数的低位字节(0~7bit)存储在内存的低地址处高位字节(23~31bit)存储在内存的高地址处大端字节序则是指整数的高位字节存储在内存的低地址处,低位字节存储在内存的高地址处

现代PC大多采用小端字节序,因此小端字节序又被称为主机字节序。当格式化的数据在两台可能不同字节序的主机之间直接传递时,接收端可能错误地解释之。解决方法是:发送端总是把要发送地数据转化为大端字节序后再发送,而接收端知道对方传送过来地数据总是采用大端字节序,所以接收端可以根据自身采用地字节序决定是否对接收到地数据进行转换(小端机转换而大端机不转换)。因此大端字节序也被称为网络字节序,它为所有接收数据地主机提供了一个正确解释收到地格式化数据的保证。需要指出的是:即使是同一台机器上的两个进程(例如一个由C++编写,另一个由JAVA编写)通信,也要考虑字节序的问题(JAVA虚拟机采用大端字节序)。

4.2 使用union判断byteorder

使用以下函数可直接判断当前主机采用的字节序是大端还是小端:

#include <stdio.h>

void byteorder() {
    union {
        short value;  // 2(bit) * 1 = 2 bit
        char bytes[sizeof(short)];  // 1(bit) * 2 = 2 bit
    }test;

    test.value = 0x0102;

    if ((test.bytes[0] == 2) && (test.bytes[1] == 1))
        printf("little endian\n");
    else if ((test.bytes[0] == 1) && (test.bytes[1] = 2))
        printf("big endian\n");
    else
        printf("unknown...\n");
}

4.3 Linux下的大小端字节序转换函数

Linux提供了以下4个函数来完成主机字节序和网络字节序之间的转换:

#include <netinet/in.h>

unsigned long int htonl(unsigned long int hostlong);
unsigned short int htons(unsigned short int hostlong);
unsigned long int ntohl(unsigned long int netlong);
unsigned short int ntohs(unsigned long int netshort);

它们的含义很明确:htonl表示“host to network long”,即将长整型(32bit)的主机字节序数据转化为网络字节序数据。这4个函数中,长整型函数通常用于转换IP地址,短整型函数常用于转换端口号。

5 源码获取

[GitHub] Interview-Prep -> union-byteorder

6 参考文献

  1. 【C++】union
  2. 《Linux高性能服务器编程》P70~71