@[TOC]
结构体
结构体的定义
结构体是一些值的集合,这些值被称为成员变量
结构体大小计算
在计算机内部只要在内存中创建一个变量,那么在内存中就会开辟相应的内存空间,那么在开辟结构体的时候会在内存中开辟多大的空间?
==这就需要引出结构体大小究竟是怎么计算的==
结构体的对齐规则
在介绍结构体大小之前,必须知道结构体的对齐规则是什么样的:
1.第一个成员在结构体变量偏移为0处。
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数=编译器默认的对齐数和该成员变量大小的较小值
本篇利用vs进行演示,vs的默认对齐数为8
3.==结构体的总大小为最大对齐数的整数倍==
4.如果出现嵌套结构体的情况,那么嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数的整数倍。
关于对齐规则的解释
我们来举一个例子
#include <stdio.h>
struct s
{
char a;
int b;
char c;
};
int main()
{
struct s s1;
printf("%d", (int)sizeof(s1));
return 0;
}
这是一个计算结构体大小的例子,我们根据对齐规则进行分析
1.第一个成员在结构体变量偏移为0处
假设我们在内存中开辟了一段空间如右侧所示,那么第一个成员在内存中就存储在偏移为0的地方
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
==对齐数=编译器默认的对齐数和该成员变量大小的较小值==
关于这句话,第二个成员的类型是int,在内存中所占空间大小为4个字节,所以在这里对齐数就是4,因此第二个成员就存储在了整数倍的地址处,而4的整数倍就是4,因此是这样存储的,而第一步创建的空间后面的部分就被编译器浪费掉了
那么问题就来了,为什么编译器要这样进行内存的浪费?后面进行介绍
结构体的总大小为最大对齐数的整数倍
结构体的总大小为最大对齐数的整数倍,因此在这里关于char c进行创建时,虽然它本身只占用1个字节,但依旧会开辟4个字节,剩下的被浪费,于是整个结构体所占空间就是12个字节。
以上就是关于结构体大小的计算基本规则,还剩余嵌套规则
下面是关于嵌套规则的例子:
#include <stdio.h>
struct S3
{
double d;
char c;
int i;
};
struct S4
{
char c1;
struct S3 s3;
double d;
};
int main()
{
printf("%d", (int)sizeof(struct S4));
return 0;
}
前面规则略过,直接看嵌套规则
根据前面的规则,可以计算出S3所占的空间是16个字节,那么在S4中,c1所占一个字节,紧接着由嵌套规则:==嵌套的结构体对齐到自己的最大对齐数的整数倍处==,也就是说,这里的结构体的最大对齐数就是s3中的double类型,也就是8个字节,所以s3这个结构体的最齐数就是8,因此后面浪费了7个字节的内容,在偏移量为8的地方接上了嵌套的s3,于是总共占用的是32个字节。
为什么C语言要这样进行大小设定?
看完对齐规则,心中难免产生这样的疑惑,为什么要这样进行大小设定?难道总字节数等于每一个元素所占字节数相加不合适吗?为什么要浪费那些空间?
==在参考资料中给出了这样的回答==
平台移植原因
1.平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
追求高性能
2.性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
我们重点解释第二个原因:
假设结构体在内存中并不是按照这个规则对齐,而是挨着进行占用
那么在访问结构体内部成员时,会发生下面的情况:
假设我们要进行成员的访问,不管按照多少个字节进行访问,都无法顺利的一次访问一个成员,但如果我们按照对齐规则进行内存的分配,就显得顺利许多
从这里可以看出,采用对齐规则在访问时确实可以达到更加高效的访问效率,可以理解为用内存换取时间效率
修改默认对齐数
==#pragma==这个预处理指令,可以改变我们的默认对齐数
用法如下面的例子所示
#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));
printf("%d\n", sizeof(struct S2));
return 0;
}
输出的结果是12和6,由此可见,用这样的宏预处理确实可以达到更改默认对齐数的效果
由此我们可以得出结论
==结构在对齐方式不合适的时候,我么可以自己更改默认对齐数==
结构体传参问题
先说结论,结构体传参最好用地址传参
#include <stdio.h>
struct S
{
int data[1000];
int num;
};
struct S s = {
{
1,2,3,4}, 1000 };
void print1(struct S s)
{
printf("%d\n", s.num);
}
void print2(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
print1(s);
print2(&s);
return 0;
}
原因是什么?
由之前的函数栈帧的创建和销毁可以知道,函数进行传参时,参数会进行压栈,会有时间和空间上的系统开销
于是最好利用的是地址传参