C++结构体

结构体类型

结构体对象

结构体对象的定义形式

定义结构体对象有三种形式:

  1. 先定义结构体类型,再定义结构体对象

    1
    2
    3
    4
    5
    struct DATE{
    int year, month, day;
    };
    DATE a, b; // C++的方式
    struct DATE c, d; // C语言的方式
  2. 定义结构体类型的同时定义结构体对象

    1
    2
    3
    struct DATE{
    int year, month, day;
    }d1, d2;
  3. 直接定义结构体对象

    1
    2
    3
    struct {
    int year, month, day;
    }d1, d2;

结构体对象的内存形式

结构体各成员是根据在结构体定义时出现的顺序依次分配空间的,其内存长度是各个成员内存长度之和,推荐使用sizeof运算,由编译器自动确定内存长度。

注意:在有的编译器中,sizeof得到的结构体内存长度可能比理论值大。如:

1
2
3
4
5
6
7
8
9
10
11
12
struct A
{
int a; // 4 字节
char b; // 1 字节
short c; // 2 字节
};
struct B
{
char b; // 1 字节
int a; // 4 字节
short c; // 2 字节
};

这两个结构体类型成员相同(仅顺序不同),理论上它们的内存长度都是4+1+2=7。但实际上sizeof(A)的结构为8,sizeof(B)的结构为12。

原因:为了加快数据存储的速度,编译器默认情况下会对结构体成员和结构体本身(其他数据成员也是如此)存储位置进行处理,使其存放的起始位置是一定字节数的倍数,而不是顺序存放,称为字节对齐

设对齐字节数为n(n=1, 2, 4, 8, 16),每个字节内存长度为Li,Max(Li)为最大的成员内存长度。字节对齐的规则是:

  1. 结构体对象的起始位置能够被Max(Li)所整除;
  2. 结构体中每个成员相对于起始地址的偏移量,即对齐值应是min(n, Li)的倍数。若不满足对齐值的要求,编译器会在成员之间填充若干个字节,称为internal padding;
  3. 结构体的总长度值应是min(n, Max(Li))的倍数,若不满足总长度的要求,编译器在为最后一个成员分配空间后,会在其后填充若干个字节,称为trailing padding。

VC默认的对齐字节数为n=8,则A与B的内存长度分析如下:

  • A的第一个成员a为int,对齐值min(n, sizeof(int))=4,成员a相对于结构体起始地址从0偏移开始,满足4字节对齐要求;
  • 第二个成员b为char,对齐值min(n, sizeof(char))=1,b紧接着a后面从偏移4开始,满足1字节对齐要求;
  • 第三个成员c为short,对齐值min(n, sizeof(short))=2,如果c紧接着b后面从偏移5开始就不满足2字节对齐要求,因此需要补充一个字节,从偏移6开始存储。

结构体A的内存长度为4+1+1(补充)+2=8

  • B的第一个成员b为char,对齐值min(n, sizeof(char))=1,成员b相对于结构体起始地址从0偏移开始,满足1字节对齐要求;
  • 第二个成员a为int,对齐值min(n, sizeof(int))=4,如果a紧接着b后面从偏移1开始,不满足4字节对齐要求,因此补充3个字节,从偏移4开始存储;
  • 第三个成员c为short,对齐值min(n, sizeof(short))=2,c紧接着a后面从偏移8开始,满足2字节对齐要求。
  • 则当前总的内存长度为1+3(补充)+4+2=10,由于n大于最大的成员内存长度4,故结构体长度应是4的倍数,因此最后需要再补充2个字节。

结构体B的内存长度为1+3(补充)+4+2+2(补充)=12

A与B字节对齐示意

使用预处理命令#progma pack(n)可以设定对齐字节数n(n=1, 2, 4, 8, 16)。例如:

1
2
3
4
5
6
7
8
9
#progma pack(push) // 保存对齐字节数
#progma pack(1) // 设定对齐字节数为1
struct A
{
int a; // 4 字节
char b; // 1 字节
short c; // 2 字节
};
#progma pack(pop) // 恢复对齐字节数

此时sizeof(A)的结果为7。

Reference