结构体,位段问题
- 一、结构体
- 二、结构体内存分配问题
- 三、存在内存对齐的原因
- 四、结构体传参问题
- 五、结构体实现位段
一、结构体
1.简单说说结构体是什么?
结构体就是把不同的数据类型整合到一起,组成的一个数据类型!!
2.结构体的创建和初始化
struct Stu
{
char name[20];
int age;
char sex[10];
char id[20];
};
int main()
{
struct Stu s1 = { "zhangsan",20,"男","202131705118" };
struct Stu s2 = {.age = 20,.id = "110",.name = "魏江南",.sex = "女"};
printf("%s %d %s %s\n", s1.name, s1.age, s1.sex, s1.id);
printf("%s %d %s %s\n", s2.name, s2.age, s2.sex, s2.id);
return 0;
}
3.匿名结构体类型
//匿名结构体类型
//没有写结构体名称
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20], *p;
p = &x;
这个匿名结构体可以理解为两次定义,x,a[20],*p没有关系,因此会报错!
1.编译器会把上⾯的两个声明当成完全不同的两个类型,所以是⾮法的。
2.匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使⽤⼀次。
4.结构体的自引用
结构体的子引用也就是定义链表节点的功能!
struct SLTNode
{
int val;
struct SLTNode* next;
}
再写一下就是typedef一下,重新起一个名字!!!
二、结构体内存分配问题
1.对齐规则。
* 结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处
*其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处
&&// 对⻬数 = 编译器默认的⼀个对⻬数 与 该成员变量⼤⼩的较⼩值。
- VS 中默认的值为8
- Linux中 gcc 没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩
*结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的整数倍。
* 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。
解析:(1)第一个char直接放,第二个int对齐数为4,所以前面为4个字节,后面放了4个字节,共8个字节,还有一个char没放,放完之后9个字节,结构体的总大小为最大对齐数的整数倍,最大对齐数为4,所以是12个字节。
(2)第二问,第一个是char,第二个是char,2个字节,第三个是int,前面要4个字节才对齐,所以总共是8个字节。
struct S3
{
double d; //8
char c; //1
int i; //4
};
int main()
{
printf("%d\n", sizeof(struct S3)); //16
return 0;
}
上面double 8个字节,char1个字节,要对齐所以共12个字节,int 4个字节,所以共16个字节。
struct S3
{
double d; //8
char c; //1
int i; //4
};
//
//int main()
//{
// printf("%d\n", sizeof(struct S3)); //16
// return 0;
//}
struct S4
{
char c1; //1
struct S3 s3; //16
double d;
};
//8+16+8=32
int main()
{
printf("%d\n", sizeof(struct S4));//32
return 0;
}
第一个char为1个字节,struct S3 s3为16个字节,里面的最大对齐数为8个字节,所以总共是8+16 = 24个字节,最后一个double 是8个字节,正好对齐,来放double ,总共24+8 = 32个字节!!!
三、存在内存对齐的原因
1.平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2.性能原因
数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于,为了访问未对⻬的内存,处理器需要作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成8的倍数,那么就可以⽤⼀个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两个8字节内存块中。
这儿在说一下这个内存对齐的优势,就是浪费一点点空间,内存对齐取数据的效率高一点,是以空间换取时间的做法!
那在设计结构体的时候,我们既要满⾜对⻬,⼜要节省空间,如何做到:
让占⽤空间⼩的成员尽量集中在⼀起
//例如:
struct S1
{
char c1;
int i;
char c2;
};
/
struct S2
{
char c1;
char c2;
int i;
};
S1 和 S2 类型的成员⼀模⼀样,但是 S1 和 S2 所占空间的大小有了⼀些区别
3.修改默认对齐数
#pragma 这个预处理指令,可以改变编译器的默认对齐数。
#pragma pack()
…
#pragma pack()
四、结构体传参问题
结构体传参,怎么传,传地址还是复制传整个结构体,答案是传地址,地址占用空间小,可以一部到位,也可以通过地址直接改变结构体的值!
struct S
{
int data[100];
int num;
};
void print1(struct S s)
{
printf("%d\n", s.num);
}
void print2(struct S *s)
{
printf("%d\n", s->num);
}
int main()
{
struct S s = { .data = {0},.num = 1 };
print1(s);
print2(&s);
return 0;
}
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递⼀个结构体对象的时候,结构体过⼤,参数压栈的的系统开销⽐较⼤,所以会导致性能的下降。
五、结构体实现位段
1.什么是位段?
**位段的成员必须是int,unsigned int, signed int
**位段的成员名后面有一个数字和一个冒号!
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
int main()
{
printf("%zd\n", sizeof(struct A));
return 0;
}
2.位段的内存存储问题!!!
**位段的成员可以是unsigned int ,signed int ,char类型
**位段的空间是按需要以4个字节或1个字节来开辟的!
**位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使⽤位段。
在vs2022中,由上面例子可以看出内存是由一个字节从右到左开辟,如果剩余空间开辟的内存不够,则另开辟一个字节,重新存储数据!!!
3.位段的跨平台问题!
1. int 位段被当成有符号数还是⽆符号数是不确定的。
2. 位段中最⼤位的数⽬不能确定。(16位机器最⼤16,32位机器最⼤32,写成27,在16位机器会
出问题。
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4. 当⼀个结构包含两个位段,第⼆个位段成员⽐较⼤,⽆法容纳于第⼀个位段剩余的位时,是舍弃剩余的位还是利⽤,这是不确定的。
位段是以节省空间为目的而创造出来的数据类型,他在申请空间时,是按比特位的形式来存储的,但是它存在跨平台问题!!!
4.位段的应用
5.位段使用的注意事项!!!
位段的⼏个成员共有同⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的。内存中每个字节分配⼀个地址,⼀个字节内部的bit位是没有地址的。
所以不能对位段的成员使⽤&操作符,这样就不能使⽤scanf直接给位段的成员输⼊值,只能是先输⼊放在⼀个变量中,然后赋值给位段的成员。
这个问题就是说,它的位段成员分配空间是按比特位来分配的,不是按字节数分配的,会存在两个位段成员有同一个地址问题,因此不能&操作!!!
完结!!!!