参考:https://blog.csdn.net/liukun321/article/details/6974282?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-2.readhide&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-2.readhide
两个原则:
原则一:结构体中元素是按照定义顺序一个一个放到内存中去的,但并不是紧密排列的。从结构体存储的首地址开始,每一个元素放置到内存中时,它都会认为内存是以它自己的大小来划分的,因此元素放置的位置一定会在自己宽度的整数倍上开始(以结构体变量首地址为0计算)。
原则二:在经过第一原则分析后,检查计算出的存储单元是否为所有元素中最宽的元素的长度的整数倍,是,则结束;若不是,则补齐为它的整数倍。

好的,现在我们已经学会结构体的对齐问题了,下面我们看两个例子来检测一下吧!

struct AA {
int a;      
char b;  
char c;     
short d;  
};
// sizeof(struct AA) = 8
struct AA {
int a;      
char b;  
short c;     
char d;  
};
// sizeof(struct AA) = 12
#include <stdio.h>
typedef struct {
    char a;
    int* b;
    char c;
}test;

typedef struct {
    char a;
    test b;
    char c;
}node;

int main(){
    printf("%d   ", sizeof(test));
    printf("%d \n", sizeof(node));
    return 0;
}
// 输出为 12  20

好了,这次的分享就到这里了,感谢阅读~

五.修改默认对齐数

我们通常使用如下的预处理命令来修改编译器的默认对齐数:
#pragma pack()
如果()里面不加数字,则默认为编译器的默认对齐数,(一般认为 n 的默认值为 8。)
我们修改的时候,只需在()里加一个数字就行
取消的时候再添加一次#pragma pack() 即可
注:
1、再()里添加的数字,我们通常加的都是2的多少次方
2、如果#pramga pack(n)中的n大于结构体成员中任何一个成员所占用的字节数,
则该n值无效。编译器会选取结构体中最大数据成员的字节数为基准进行对齐

下面来举一个实例:

#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8
struct S1{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认

#pragma pack(1)//设置默认对齐数为1
struct S2
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{
//输出的结果是什么?
printf("%d \n", sizeof(struct S1));//12
printf("%d \n", sizeof(struct S2));//6
return 0;
}

再次补充

#pragma pack(4)  
struct Test3  
{  
    char c;  
    short sh;  
    int a;  
    float f;  
    int *p;  
    char *s;  
    double d;  
    static double sb;  
    static int sn;  
}; 
// sizeof(struct Test3) = 28
// 静态变量储存在静态区,因此sizeof的时候不会计算静态变量的大小

再再再次补充:

位域的存储

C语言标准并没有规定位域的具体存储方式,不同的编译器有不同的实现,但它们都尽量压缩存储空间。

位域的具体存储规则如下:

1) 当相邻成员的类型相同时,如果它们的位宽之和小于类型的 sizeof 大小,那么后面的成员紧邻前一个成员存储,直到不能容纳为止;如果它们的位宽之和大于类型的 sizeof 大小,那么后面的成员将从新的存储单元开始,其偏移量为类型大小的整数倍。

以下面的位域 bs 为例:

#include <stdio.h>

int main(){
    struct bs{
        unsigned m: 6;
        unsigned n: 12;
        unsigned p: 4;
    };
    printf("%d\n", sizeof(struct bs));  // 输出为 4 

    return 0;
}

2) 当相邻成员的类型不同时,不同的编译器有不同的实现方案,GCC 会压缩存储,而 VC/VS 不会。

请看下面的位域 bs:

#include <stdio.h>

int main(){
    struct bs{
        unsigned m: 12;
        unsigned char ch: 4;
        unsigned p: 4;
    };
    printf("%d\n", sizeof(struct bs));

    return 0;
}

在 GCC 下的运行结果为 4,三个成员挨着存储;在 VC/VS 下的运行结果为 12,三个成员按照各自的类型存储(与不指定位宽时的存储方式相同)。
3) 如果成员之间穿插着非位域成员,那么不会进行压缩。例如对于下面的 bs:

struct bs{
    unsigned m: 12;
    unsigned ch;
    unsigned p: 4;
};

在各个编译器下 sizeof 的结果都是 12。

无名位域

位域成员可以没有名称,只给出数据类型和位宽,如下所示:

struct bs{
    int m: 12;
    int  : 20;  //该位域成员不能使用
    int n: 4;
};

无名位域一般用来作填充或者调整成员位置。因为没有名称,无名位域不能使用。

上面的例子中,如果没有位宽为 20 的无名成员,m、n 将会挨着存储,sizeof(struct bs) 的结果为 4;有了这 20 位作为填充,m、n 将分开存储,sizeof(struct bs) 的结果为 8。