结构体–字节对齐/位域

字节对齐

字节对齐规则

结构体如下所示:

1
2
3
4
5
typedef struct A {
char a;
int b;
short c;
};

按理来说,char占用1个字节,int占用4个字节,short占用2个字节,因此总共占用7个字节。
但实际上,运行下列代码:

1
2
3
4
5
6
7
8
9
int main()
{
A x;
printf("%d \n",sizeof(x.a));
printf("%d \n", sizeof(x.b));
printf("%d \n", sizeof(x.c));
printf("%d \n", sizeof(x));
return 0;
}

得到:
struct
字节对齐需满足以下规则:

  • 结构体变量的首地址能够被其最宽基本类型成员的大小所整除。
  • 结构体每个成员相对结构体首地址的偏移量(offset)都是自身有效对齐字节数的整数倍。
  • 结构体内变量的自身有效对齐字节数为自身对齐字节数与系统对齐字节数的较小者;结构体的大小为成员最大自身对齐字节数的倍数。

根据字节对齐机制,按结构体中占最多的字节申请,所以申请空间是按照sizeof(int)大小申请,首先先申请 a 的空间,char 占一个字节未占满,填充3个空字节。然后再申请 b 的空间,刚好4个字节,最后再填 c,占2个字节,但还得填充2个字节。总共12个字节。

如果调整一下结构体的顺序

1
2
3
4
5
typedef struct A {
char a;
short c;
int b;
};

再次运行上面代码,得到:
struct
根据字节对齐机制,按结构体中占最多的字节申请,申请空间按照sizeof(int)大小申请,首先先申请 a 的空间,char 占一个字节未占满,填充1个空字节。然后再申请 c 的空间,2个字节,与 a 共同填满4个字节,最后再填 b,占4个字节。总共8个字节。

  • 系统默认对齐字节数:系统默认对齐的字节数
  • (变量)自身对齐字节数:变量自身sizeof()得到的大小
  • (变量)自身有效对齐字节数:min(自身对齐字节数,系统默认对齐字节数)
  • (结构体)最大对齐字节数:max(结构体中所有变量的自身有效对齐字节数)
    对齐规则可以理解为:
  • 条件①:结构体当前大小%当前变量自身有效对齐字节数=0,如果无法对齐,则持续填充字节直至对齐。
  • 条件②:结构体的总大小%结构体最大对齐字节数=0,如果无法对齐,则持续填充字节直至对齐。

改变系统默认值后的结构体字节对齐

  • 通过 #pragma pack() 命令可以指定默认对齐字节数,可选参数有1/2/4/8/16,不带参或参数非以上值时,将恢复默认值。
  • 这一对代码经常成对出现,保证只有自己使用,防止对之后结构体的字节对齐产生影响。
    将上面代码改为:
    1
    2
    3
    4
    5
    6
    7
    #pragma pack(2)
    typedef struct A {
    char a;
    int b;
    short c;
    };
    #pragma pack()
    原本为12个字节,改变默认对齐后为8个字节。
    1
    2
    3
    4
    5
    6
    7
    #pragma pack(1)
    typedef struct A {
    char a;
    int b;
    short c;
    };
    #pragma pack()
    改为1则为7个字节。

位域

什么是位域

有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个bit。例如在存放一个开关量时,只有 0 和 1 两种状态,用 1 bit即可。为了节省存储空间,并使处理简便,C 语言又提供了一种数据结构,称为”位域”或”位段”。
所谓”位域”是把一个字节中的二进制位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
struct A {
int a : 8;
int b : 1;
int c : 2;
int d : 1;
int e : 4;
int f : 16;
};
int main()
{
printf("%d \n", sizeof(A));
return 0;
}

结构体 A 总共占4个字节。

union与位域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
typedef union STATE {
struct BITDATA {
int D0 : 1;
int D1 : 1;
int D2 : 1;
int D3 : 1;
int D4 : 1;
int D5 : 1;
int D6 : 1;
int D7 : 1;
}BIT;
int value;
};

int main()
{
int a = 0xff;
printf(" %d\n", a);
STATE* temp = NULL;
temp = (STATE*)&a;
temp->BIT.D0 = 0;
printf(" %d\n",a);
return 0;
}

运行结果:
struct
使用union和位域实现对变量的某一bit位进行操作;该方式可用于计算机操作底层硬件寄存器。

注意点

  1. 位域成员必须声明为整型int、unsigned int或signed int类型,或是char,unsigned char,但不能是浮点型包括float,double
  2. 位域的长度不能超过它所依附的数据类型的长度,成员变量都是有类型的,这个类型限制了成员变量的最大长度,: 后面的数字不能超过这个长度。
  3. 给位域赋值应注意赋值不能超过该位域的允许范围
    例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    typedef struct STU {
    int age : 8;
    };

    int main()
    {
    STU a;
    a.age = 600;
    printf("%d \n", a.age);
    return 0;
    }
    输出不是 600,而是 88。
    600 二进制: 10 0101 1000
    88 二进制: 0101 1000
  4. 位域可以是无名位域,这时它只用来作填充或调整位置。无名的位域是不能使用的(没有名称当然没法访问)。
  5. 当无名位域且长度度为0时,表示下一个变量从下一段地址开始存储。下一段地址的具体位置还得根据结构体的内存对齐原则.
    1
    2
    3
    4
    5
    6
    struct A {
    char a : 1;
    char : 0; // 空域,表示当前字节剩余7位用0填充表示不用
    char c : 4;
    char d : 2;
    };
    结构体 A 大小为2个字节。