1结构体内存对齐
接着上次没描述完的内存对齐
1.1为什么存在内存对齐?
大部分的参考资料都是这样说的:1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2.性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。假设一个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用一个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个8字节内存块中。
总结概括:结构体的内存对齐就是通过空间来换取时间的做法。
我们在设计结构体时,我们是可以做到内存对齐和空间节约的那就是让内存占比小的成员放在一起
通过这两种图的对比就很直接的验证了为什么内存小的要放在一起。
1.2修改默认对齐数
#pragama 这个命令就是用来修改编译器默认对齐数的,下面给大演示一下
#pragma pack() 括号里面就是你想修改默认对齐数的值,括号里面不写值就还原编译器默认对齐数。通过#pragma pack() 这个命令可以让我们更好的使用。
1.3结构体传参
其实这个在上个博客就提到了一些有关传参
struct S
{
int arr[100];
int num;
};
struct S s = { {1,2,3,4,5,6},10 };//初始化
//传参法1
void Print1(struct S s)//结构体
{
printf("%d", s.num);
}
//法2
void Print2(struct S* s)//结构体指针
{
printf("%d", s->num);
}
int main()
{
Print1(s);
Print2(&s);
return 0;
}
那应该选择哪种传参方式呢?答案是法2也就是结构体指针作为参数
原因:在函数传参时,参数是需要压栈的,会对空间和时间上进行系统开销。
前面我们了解了结构体的内存储存,发现结构体会浪费很多空间,参数在压栈时会开销过大,导致性能下降。
小结:结构体在传参时最好是传结构体的地址
2 位段
2.1什么是位段呢?
我们先要知道位段是基于结构体实现的。
因为位段是基于结构体的所以和结构体是相似的,就说说不同点
1.位段必须是int,unsigned int ,singed int ,在C99里面位段的其他成员类型也行。
2.位段的成员名后面会跟着一个冒号和数字
strucr B
{
int a:2;//数字表示占几个bit位 这里是2个bit
int b:5;//5个bit
int c:10;//10个bit
int d:30;//30个bit
};
位段的作用就是用来节约空间的!!!,其次还是二进制位
用来节约空间的我们必然会好奇它的大小是多少?
2.2位段内存的分配
1.位段的成员可以是int unsigned int signed int或者是char等类型
2.位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的。
3.位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
4.在VS编译器中是从左向右储存的。
5.如果剩余空间不够下一个成员使用,就会浪费空间,开辟新的空间。
#incldue<stdio.h>
strucr B
{
int a:2;//数字表示占几个bit位 这里是2个bit
int b:5;//5个bit
int c:10;//10个bit
int d:30;//30个bit
};
int main(
{
printf("%d",sizeof(struct B));
return 0;
};
上面的代码运行结果是8个字节!为什么呢?听我给我你“吹”
全部都是int 类型的也就是4个字节 32个bit位。我们就先开辟一个4字节的空间,32个bit位。
a用了2个,b用了5个,c用了10,d要用30个但是不够,所以我们还要在开辟一个4过字节的空间。
所以一起就用了8个字节。
2.3位段跨平台问题
1.int 位段被当做有符号还是无符号的不能确定。
2.位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
3.位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4.当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
小结:跟结构相比,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。
2.4位段的使用注意事项
位段的几个成员共有同一个字节,这样有些成易的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的。内存中每个字节分配一个地址,一个字节内部的bit位是没有地址的。
所以不能对位段的成员使用&操作符,这样就不能使用scanf直接给位段的成员输入值,只能是先输入放在一个变量中,然后赋值给位段的成员。
#include<stdio.h>
struct S
{
int a:2;
int b:5;
int c:10;
int d:20;
};
int main()
{
struct S s={0};
scanf("%d",&s.a);//err
int num=1;
scanf("%d",&num);
s.a=num;
return 0;
}
好了结构体就给大家介绍完了。欢迎大家在评论区补充!!