C的实用笔记39——结构体占用内存大小(了解)
1.结构体成员列表的存储现象
1、知识点:
- 我们在结构体这个整体中定义的成员变量是挨着的,这让我们容易误以为它们的存储方式也是挨着的,但其实并不是。
- 我们之前用sizeof测过,在gcc编译器下,不论什么类型指针,占用空间都是8字节;在VS编译器中,不论什么类型的指针,占用空间都是4字节。
- 在gcc编译器下,操作系统的1个操作字是8个字节(等于地址的存储范围,即寻址范围),因此每次存储、读取数据的都是按照8个字节来的。
2、一个有趣的现象:
- 例1:
#include <stdio.h> struct Student { int num; char name[32]; char sex; double score; }; int main(int argc, char const *argv[]) { struct Student stu1; int sizeOfStudent = sizeof(struct Student); int sumOfMember = sizeof(stu1.num)+sizeof(stu1.name)+sizeof(stu1.sex)+sizeof(stu1.score); printf("结构体占用内存大小 :%d\n", sizeOfStudent); printf("成员变量占用内存大小之和:%d\n", sumOfMember); return 0; }
- 例2:
#include <stdio.h> struct Student { int num; char sex; double score; }; int main(int argc, char const *argv[]) { struct Student stu1; int sizeOfStudent = sizeof(struct Student); int sumOfMember = sizeof(stu1.num)+sizeof(stu1.sex)+sizeof(stu1.score); printf("结构体占用内存大小 :%d\n", sizeOfStudent); printf("成员变量占用内存大小之和:%d\n", sumOfMember); return 0; }
- 例3:
#include <stdio.h> struct Student { char sex; double score; }; int main(int argc, char const *argv[]) { struct Student stu1; int sizeOfStudent = sizeof(struct Student); int sumOfMember = sizeof(stu1.sex)+sizeof(stu1.score); printf("结构体占用内存大小 :%d\n", sizeOfStudent); printf("成员变量占用内存大小之和:%d\n", sumOfMember); return 0; }
3、思考:
- 结构体中成员变量的生长方向是从低到高还是从高到低呢?答案可以肯定的是,从低到高。我们在例3中显示两个成员变量的地址:
#include <stdio.h> struct Student { char sex; double score; }; int main(int argc, char const *argv[]) { struct Student stu1; printf("成员变量sex的地址 : 0x%p\n", &stu1.sex); printf("成员变量score的地址: 0x%p\n", &stu1.score); return 0; }
- 结构体的占用空间看起来是某个数的倍数,那么这个数是什么呢?和什么有关呢?从例1、例2、例3中来看,这个数应该和成员变量的类型有关,现在无法确定。答案在规则1(为结构体开辟内存的基本单位)
- 结构体的占用空间与成员变量的数目有没有关系呢?答案是否定的。可以比较例2和例3,例2比例3多出一个成员变量,但是它们的结构体占用空间大小都是16字节。
- 结构体中成员变量既然不是挨着存储的,那么到底要间隔多少存储呢?和什么有关呢?我们可以对例2和例3中的成员变量取地址进行验证,间隔存储的字节数应该和成员变量的类型有关,但现在无法确定。答案在规则2(内存对齐原则)。
- 是否有可能让结构体的占用空间能够尽可能小以至于不浪费空间?甚至等于成员变量占用空间之和呢?答案是可以的,方法①把占用空间小的成员变量写在成员列表上面,占用空间大的成员变量写在成员列表下面。但这并不是都有效,需要视情况。我们可以比较第3部分的案例1和案例2;方法②规则3(指定对齐)
2.结构体内存分配规则
1、规则一(根据成员变量类型确定为结构体开辟内存的基本单位):与成员变量类型相关
- 知识点1【规则1制定的原因】:①能不能避免麻烦,直接给结构体分配一个很大的内存呢?当然不行,这是为了不造成过多的空置内存;②能不能对任意类型的结构体都规定一样的开辟基本单位?当然不行,这是为了针对不不同类型的结构体,制定不同的开辟速度,为了节省开辟时间;
- 知识点2【确定方法】:确定每个成员变量的开辟内存大小后,取大的那一个作为结构体开辟内存的单位
- 确定不同类型成员变量的开辟内存大小:
- char类型的成员变量:以1字节为单位开辟内存
- short类型的成员变量:以2字节为单位开辟内存
- int类型的成员变量:以4字节为单位开辟内存
- float类型的成员变量:以4字节为单位开辟内存
- double类型的成员变量:以8字节为单位开辟内存
- 任意指针类型的成员变量:以8字节为单位开辟内存
- 数组成员变量:把它看成上述基本类型的成员变量的集合
2、规则二(内存对齐原则):与成员变量类型相关
- 知识点【规则2制定的原因】:①能不能避免麻烦,在确定了结构体开辟内存的基本单位之后,直接在让成员变量挨着存储呢?当然不行,因为操作系统的1个操作字是8个字节,所以如果不规定字节对齐的话,对一个结构体成员取值,可能要取两遍,归纳起来就是用空间来换时间,提高CPU读取数据效率。
- 内存对齐,跟谁对齐?答案是结构体变量的首地址,也就是结构体变量中第一个成员变量的地址。
- 确定不同类型成员变量的对齐方法:假设将结构体变量的首地址看作基准0,那么某个成员变量开始存放的地址编号是该成员的数据类型所占内存大小的倍数。
- char类型的成员变量:与结构体变量首地址的相对地址是1字节的倍数。
- short类型的成员变量:与结构体变量首地址的相对地址是2字节的倍数。
- int类型的成员变量:与结构体变量首地址的相对地址是4字节的倍数。
- float类型的成员变量:与结构体变量首地址的相对地址是4字节的倍数。
- double类型的成员变量:与结构体变量首地址的相对地址是8字节的倍数。
- 任意指针类型的成员变量:与结构体变量首地址的相对地址是8字节的倍数。
- 数组成员变量:把它看成上述基本类型的成员变量的集合
3.使用结构体内存分配规则
1、案例1:
#include <stdio.h>
struct Student
{
char a;
short b;
int c;
};
int main(int argc, char const *argv[])
{
struct Student stu1;
int sizeOfStudent = sizeof(struct Student);
int sumOfMember = sizeof(stu1.a)+sizeof(stu1.b)+sizeof(stu1.c);
printf("结构体占用内存大小 : %d\n", sizeOfStudent);
printf("成员变量占用内存大小之和: %d\n", sumOfMember);
printf("成员a的地址: 0x%p\n", &stu1.a);
printf("成员b的地址: 0x%p\n", &stu1.b);
printf("成员c的地址: 0x%p\n", &stu1.c);
return 0;
}
2、案例2:把案例1中后两个成员变量b和c调换个位置
#include <stdio.h>
struct Student
{
char a;
int c;
short b;
};
int main(int argc, char const *argv[])
{
struct Student stu1;
int sizeOfStudent = sizeof(struct Student);
int sumOfMember = sizeof(stu1.a)+sizeof(stu1.b)+sizeof(stu1.c);
printf("结构体占用内存大小 : %d\n", sizeOfStudent);
printf("成员变量占用内存大小之和: %d\n", sumOfMember);
printf("成员a的地址: 0x%p\n", &stu1.a);
printf("成员c的地址: 0x%p\n", &stu1.c);
printf("成员b的地址: 0x%p\n", &stu1.b);
return 0;
}
3、案例3:
#include <stdio.h>
struct Student
{
char a;
double b;
};
int main(int argc, char const *argv[])
{
struct Student stu1;
int sizeOfStudent = sizeof(struct Student);
int sumOfMember = sizeof(stu1.a)+sizeof(stu1.b);
printf("结构体占用内存大小 : %d\n", sizeOfStudent);
printf("成员变量占用内存大小之和: %d\n", sumOfMember);
printf("成员a的地址: 0x%p\n", &stu1.a);
printf("成员b的地址: 0x%p\n", &stu1.b);
return 0;
}
4、案例4:
#include <stdio.h>
struct Student
{
char a[10];
int b;
};
int main(int argc, char const *argv[])
{
struct Student stu1;
int sizeOfStudent = sizeof(struct Student);
int sumOfMember = sizeof(stu1.a)+sizeof(stu1.b);
printf("结构体占用内存大小 : %d\n", sizeOfStudent);
printf("成员变量占用内存大小之和: %d\n", sumOfMember);
printf("成员a的地址: 0x%p\n", stu1.a);
printf("成员b的地址: 0x%p\n", &stu1.b);
return 0;
}
4.结构体内存分配的决策流程
1、决策流程图:
2、以案例一为例子,进行说明:
5.结构体内存的指定对齐
1、规则三(指定对齐原则):用户自定义对齐字节
- 知识点【规则3制定的原因】:人为减少空间浪费,但是缺增加了CPU读取数据的时间,即时间换空间。
- 指定对齐方法:#pragma pack(value) //预处理指令#pragma,指定对齐值为value
- 指定对齐后,规则1和规则2都会受到影响:
- 对规则1的影响:value值与默认开辟单位相比,取两者的较小值为基本单位开辟内存
- 对规则2的影响:某些成员的对齐方式受到改变,value与默认对齐值相比,取两者的较小值为内存对齐值进行对齐
2、案例:
#include <stdio.h>
#pragma pack(2) //value值指定为2
struct Student
{
char a;
int b;
};
int main(int argc, char const *argv[])
{
struct Student stu1;
int sizeOfStudent = sizeof(struct Student);
int sumOfMember = sizeof(stu1.a)+sizeof(stu1.b);
printf("结构体占用内存大小 : %d\n", sizeOfStudent);
printf("成员变量占用内存大小之和: %d\n", sumOfMember);
printf("成员a的地址: 0x%p\n", &stu1.a);
printf("成员b的地址: 0x%p\n", &stu1.b);
return 0;
}