注:本文的编程环境是visual studio2019;64位win10系统
我们先来观察这样一段代码:
#include<stdio.h>
typedef struct s1{
char c1;
char c2;
int i;
};
typedef struct s2{
char c1;
int i;
char c2;
};
int main() {
printf("%d\n",sizeof(struct s1));
printf("%d\n",sizeof(struct s2));
return 0;
}
我们看一下输出结果:
我们可以看到,两个结构体s1和s2内部的数据都是两个char类型和一个int类型数据,只是存放的顺序不同,其结构体整体的大小竟然发生了改变。这就是结构体内存对齐。
定义: 结构体内存对齐是指创建结构体变量时,编译器会根据特定规则把内存会按照特定的规则分配空间以存储结构体的成员,以提高内存访问效率和性能。
我们在一个代码案例中看到编译器输出的结构是8 / 12;但是我们知道char类型的内存大小位一个字节,int类型的内存大小位4个字节;为什么S1的内存大小是8个字节而不是6个字节呢?
为了进一步的深入研究结构体成员变量在结构体内的内存分布,我们引入了offsetof:
offsetof
是C语言中的一个宏,用于计算结构体中成员变量的偏移量(offset)。它的作用是返回指定结构体中特定成员变量的偏移量,即该成员相对于结构体起始地址的偏移量。offsetof
的使用需要引入头文件:#include <stddef.h>
cplusplus官网介绍:
我们对案例一进行测试:
#include<stdio.h>
#include <stddef.h>
typedef struct s1{
char c1;
char c2;
int i;
};
typedef struct s2{
char c1;
int i;
char c2;
};
int main() {
printf("s1的内存大小:%d\n",sizeof(struct s1));
printf("第一个成员变量与起始点的偏移量:%d\n",offsetof(struct s1,c1));
printf("第二个成员变量与起始点的偏移量:%d\n",offsetof(struct s1,c2));
printf("第三个成员变量与起始点的偏移值:%d\n",offsetof(struct s1,i));
printf("s1的内存大小:%d\n", sizeof(struct s2));
printf("第一个成员变量与起始点的偏移量:%d\n", offsetof(struct s2, c1));
printf("第二个成员变量与起始点的偏移量:%d\n", offsetof(struct s2, i));
printf("第三个成员变量与起始点的偏移量:%d\n", offsetof(struct s2, c2));
return 0;
}
输出结果如下:
那么这里的内存分布大概是什么样子呢?
结构体s1: c1的的偏移量为0,则c1的地址就是从s1的起始地址开始,占一个字节; c1的的偏移量为1,则c1的地址就是从s1的起始地址后一个字节开始,占一个字节; i的的偏移量为4,则c1的地址就是从s1的起始地址后4个字节开始,占4个字节; 结构体s2同理;
我们会发现,图片中还有一些空余的地址空间(白色区域)没有被使用,这就是被浪费掉的地址空间。
这里就解释了,为什么S1的内存大小是8个字节而不是6个字节,因为结构体的内存分配中存在未被使用的地址空间。
我们虽然通过测试,明白了案例一的内存空间分配情况。但是我们还是不知道为什么编译器会这样分配内存空间。
下面我介绍一下结构体内存对齐的规则:
对齐数 = 编译器默认的一个对齐数 与 当前成员大小的较小值(所以一般情况下也是成员大小)
visual studio的默认对齐数为8
那我们现在再来回顾案例一:
s1结构体: c1是第一个成员,在与结构体变量偏移量为0的地址处。 c2要对齐,本身大小为1,对齐数为8;所以对齐到1的整数倍的地址,即为地址1; i也要对齐,本身大小为4,对齐数为8;所以对齐到4的整数倍的地址,即为地址4; s2结构体: c1是第一个成员,在与结构体变量偏移量为0的地址处。 i本身大小为4,对齐数为8;所以对齐到4的整数倍的地址,即为地址4; c2本身大小为1,对齐数为8;所以对齐到1的整数倍的地址,,但是0~7的地址空间被占用,所以c2起始地址为8;
那么我们再来看一个结构体:
typedef struct s3 {
char i;
double d;
int c1;
};
我们来判断一下他的结构体大小;
这里可能会有些人以为是20;其实正确的结果是24。
考虑第三个规则:结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
所以在设计结构体的时候,我们让占用空间小的成员尽量集中在一起。 既满足内存对齐,又节省空间
我们在对齐规则中,我们知道visual studio的默认对齐数是8,但是gcc编译器(Linux)无默认对齐数。
那么我们可不可以对默认对齐数进行修改呢?
# pragma
预处理指令
我们可以通过
#pragma pack(1)
来使得默认对齐数为1
注:
#pragma
是编译器相关的指令,不属于 C 语言标准的一部分,因此在不同的编译器中可能会有不同的行为。#pragma
的使用可以提供一些便利,但过度依赖它可能导致代码的可移植性变差,因为不同的编译器对 #pragma
的支持程度不同。