1.修改默认对齐数
在之前我们了解到在vs中的默认对齐数为8,在gcc下没有默认对齐数的,那我们能否在vs上进行修改呢?我们来试一下:(#pragma 这个预处理指令,可以改变编译器的默认对齐数)
#include <stdio.h>
#pragma pack(1) //设置默认对⻬数为1
struct S
{char c1;int i;char c2;
};
int main()
{struct S s;size_t ret =sizeof(s);printf("%zd", ret);return 0;
}
在之前我们的这串代码结果为12,现在结果为6,虽然我们通过修改默认对齐数,而减少空间的浪费,但是我们编译器在寻找相对应的数据时也会浪费时间。这与我们了解结构体的对齐的作用相同。当我们要修改回默认对齐数时只需要再次输入#pragma pack(),即可。括号里为空。
2. 结构体传参
在之前的代码学习时,我们了解到可以将结构体整体传入,也可以传地址,但是这两种方法哪一种更好呢?当然是传地址了,因为我们传整个结构体的话,会多占用一份形参所申请的空间。
演示如下:
#include <stdio.h>
struct S
{char name[20];int age;
};void print1(struct S s[3])
{for (int i = 0; i < 3; i++){printf("%s %d ", s[i].name, s[i].age);}}void print2(struct S *pt){for (int i = 0; i < 3; i++){printf("%s %d ", pt->name, pt->age);pt++;}
}
int main()
{struct S s[] = { {"zhang",18, } ,{"li",12},{"wang",24} };print1(s);printf("\n");print2(&s);return 0;
}
这里的print2就是采用传地址的方式,函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。所以结构体传参最好使用传地址的方式。
3 .结构体实现位段
1.什么是位段
1. 位段的成员必须是 int、unsigned int 或signed int ,在C99中位段成员的类型也可以
选择其他类型。
2. 位段的成员名后边有一个冒号和一个数字。
结构体位段的出现是为了节省空间。
#include<stdio.h>
struct S
{char a:2 ;int b:20 ;int c:2 ;};
int main()
{struct S s;printf("%zd ", sizeof(s));return 0;
}
这个结构体原来占用12个字节,现在占用多少个呢?
运行结果:
2.位段的内存分配
1. 位段的成员可以是 int unsigned int signed int 或者是 char 等类型
2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
在不同编译器上位段的内存申请可能不同,下面我们来看下在vs上位段的内存申请是怎么样的?
struct S
{
char a:3;
char b:4;
char c:5;
char d:4;
};
struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
在内存中的表示:
为啥会是62 03 04 呢?我们来分析一下:
首先a我们给了它三个bit位,需要它存储的数字是10,10 的二进制为1010,存不下,发生截断只能存入010,b我们需要它存储的数字是12,它的二进制为1100也可以存下。以此内推,c中存的是00011,d中就是0100,我们一开始申请4个字节。
我们假设从右往左开始存放,如果后续空间不够就重新申请空间:一个字节一个字节的申请
我们再一个字节一个字节的算,就是62 03 04,所以假设成立。这就是在vs中申请内存的方式。
(在位段中也存在对齐现象)
3.位段的跨平台问题
1. int位段被当成有符号数还是无符号数是不确定的。
2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会
出问题。
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃
剩余的位还是利用,这是不确定的。
4.位段的应用
下图是网络协议中,IP数据报的格式,我们可以看到其中很多的属性只需要几个bit位就能描述,这里使用位段,能够实现想要的效果,也节省了空间,这样网络传输的数据报大小也会较小一些,对网络的畅通是有帮助的。
位段使用的注意事项
位段的几个成员共有同一个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的。内存中每个字节分配一个地址,一个字节内部的bit位是没有地址的。
所以不能对位段的成员使用&操作符,这样就不能使用scanf直接给位段的成员输入值,只能是先输入放在一个变量中,然后赋值给位段的成员。
演示如下:
#include<stdio.h>
struct S
{char a : 3;char b : 4;
};int main()
{int n = 0;struct S s = { 0 };scanf("%d", &n);s.a = n;return 0;
}
这样就可以在不拿到位置的情况下,赋值了。
