梁山做网站价格,建筑企业资质新规定2022,织梦cms源码,网站建设怎么做更好结构体内存对齐实现位段 一.结构体1.结构体的声明2.结构体变量成员访问操作符3.结构体传参4.匿名结构体5.结构的自引用 二.结构体内存对齐1.对齐规则2.为什么存在内存对齐#xff1f;3.修改默认对齐数 三.结构体实现位段1.什么是位段2.位段的内存分配3.位段的跨平台问题4.位段… 结构体内存对齐实现位段 一.结构体1.结构体的声明2.结构体变量成员访问操作符3.结构体传参4.匿名结构体5.结构的自引用 二.结构体内存对齐1.对齐规则2.为什么存在内存对齐3.修改默认对齐数 三.结构体实现位段1.什么是位段2.位段的内存分配3.位段的跨平台问题4.位段的应用5.位段使用的注意事项 前言 学习了数组后发现数组中的元素只能是相同类型的变量那么有没有可以存放不同类型的变量呢结构体一些值的集合这些值称为成员变量结构体的每个成员可以是不同类型的变量。
一.结构体
1.结构体的声明
struct tag
{member-list;//结构体成员列表
}variable-list;//结构体变量列表例如描述一个人
struct Person {int age;//年龄char name[50];//姓名float height;//身高
};//封号不能丢2.结构体变量成员访问操作符
结构体变量.结构体成员名。结构体指针变量-结构体成员名。
#include stdio.h
struct Person
{int age;char name[50];float height;
}p1 { 20,zhangsan,185.5 }, * ps;//全局变量(*ps结构体指针ps)int main()
{struct Person p2 { 18,lisi,173.2 };//局部变量struct Person p3 { 19,wangwu,180.8 };//局部变量ps p3;printf(%d %s %.1f\n, p1.age, p1.name, p1.height);//结构体成员访问操作符.printf(%d %s %.1f\n, p2.age, p2.name, p2.height);printf(%d %s %.1f\n, (*ps).age, (*ps).name, (*ps).height);printf(%d %s %.1f\n, ps-age, ps-name, ps-height);//结构体成员访问操作符-等价于先*再.return 0;
}3.结构体传参
传结构体。传结构体的地址。
#include stdio.h
struct Person
{int age;char name[50];float height;
};
void test1(struct Person p)//用结构体接收
{printf(%d %s %.1f\n, p.age, p.name, p.height);
}
void test2(struct Person* p)//用结构体指针接收
{printf(%d %s %.1f\n, p-age, p-name, p-height);
}
int main()
{struct Person p1 { 20,zhangsan,185.5 };test1(p1);//传结构体test2(p1);//传结构体的地址return 0;
}思考我们发现二者都可以成功访问结构体成员那二者有什么区别呢
传递结构体时其实函数内部创建了一个临时结构体变量存放传入的结构体当结构体很大时会额外占用空间不划算。本质上是值传递。传递结构体地址时只需创建4个字节结构体指针变量通过其来访问结构体成员可以大大节省空间。本质上是地址/指针传递。推荐传递结构体地址。 4.匿名结构体
//匿名结构体类型
struct//不完全声明由于没有名字无法在其之后创建变量
{int age;char name[50];float height;
}s1, s2;//在结构体声明的时候直接创建变量不能在其之后创建变量了只能使用一次
int main()
{struct s3;//error
}当只需使用一次可以使用在声明结构体时直接创建变量不能在其之后创建变量了。
思考以下代码行不行
struct
{int age;char name[50];float height;
}s1;
struct
{int age;char name[50];float height;
}*ps;int main()
{ ps s1;//?return 0;
}答案不行看似一样其实这两个结构体是不同类型的只是成员变量相同的不同结构体类型二者不兼容。没有名字导致的问题。
5.结构的自引用
比如定义一个链表的节点
struct Node
{int data;//存放数据struct Node* next;//存放指针
};二.结构体内存对齐
注意面试时计算结构体的大小是一个热门的考点一定要学会。
1.对齐规则
结构体的第一个成员对齐到和结构体变量起始位置偏移量为 0 的地址处。 偏移量该成员变量的地址距离结构体地址的字节数计算偏移量的函数offsetof。其他成员变量要对齐到某个数字对齐数的整数倍的地址处。 对齐数 编译器默认的对齐数与该成员变量大小的较小值。 在VS 中默认的对齐数值为 8 。 Linux中gcc编译器没有默认对齐数对齐数就是成员自身的大小。结构体总大小为最大对齐数结构体中每个成员变量都有⼀个对齐数所有对齐数中最大的的整数倍。如果嵌套了结构体的情况嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处结构体的整体大小就是所有最大对齐数含嵌套结构体中成员的对齐数的整数倍。
offsetof宏计算结构体成员相较于结构体变量起始位置的偏移量头文件stddef.h
例如计算结构体大小的代码。
#includestdio.h
#includestddef.h
struct S1
{char c1;//自身大小1默认对齐数8对齐数1char c2;//自身大小1默认对齐数8对齐数1int n;//自身大小4默认对齐数8对齐数4
};
struct S2
{char c1;//自身大小1默认对齐数8对齐数1int n;//自身大小4默认对齐数8对齐数4char c2;//自身大小1默认对齐数8对齐数1
};
int main()
{printf(%zd\n, offsetof(struct S1, c1));//0printf(%zd\n, offsetof(struct S1, c2));//1printf(%zd\n, offsetof(struct S1, n));//4printf(%zd\n, sizeof(struct S1));//8printf(%zd\n, offsetof(struct S2, c1));//0printf(%zd\n, offsetof(struct S2, n));//4printf(%zd\n, offsetof(struct S2, c2));//8printf(%zd\n, sizeof(struct S2));//12return 0;
}练习
#includestdio.h
struct S1
{double d;//自身大小8默认对齐数8对齐数8char c;//自身大小1默认对齐数8对齐数1int i;//自身大小4默认对齐数8对齐数4
};
struct S2
{char c1;//自身大小1默认对齐数8对齐数1struct S1 s1;//自身大小16默认对齐数8对齐数8//如果嵌套了结构体的情况嵌套的结构体成员对齐到《自己的成员中最大对齐数的整数倍处d的对齐数的整数倍处》//结构体的整体大小就是所有最大对齐数含嵌套结构体中成员的对齐数的整数倍。double d;//自身大小8默认对齐数8对齐数8
};
int main()
{printf(%zd\n, sizeof(struct S1));//16printf(%zd\n, sizeof(struct S2));//32return 0;
}2.为什么存在内存对齐 那在设计结构体的时候我们既要满足对齐又要节省空间如何做到
//例如
#includestdio.h
struct S1
{char c1;int i;char c2;
};
struct S2
{char c1;char c2;int i;
};
int main()
{printf(%zd\n, sizeof(struct S1));//12printf(%zd\n, sizeof(struct S2));//8//S1 和 S2 类型的成员⼀模⼀样但是 S1 和 S2 所占空间的大小有了⼀些区别。return 0;
}总结让占用空间小的成员尽量集中在⼀起。
3.修改默认对齐数
VS上默认对齐数为8。#pragma pack(一般为2^n) 这个预处理指令可以改变编译器的默认对齐数。例如#pragma pack(1)#pragma pack(2)#pragma pack(4)。#pragma pack() #pragma pack(8)。
#includestdio.h
#pragma pack(1)//修改默认对齐数变成1
struct S
{char c1;//自身大小1默认对齐数1对齐数1int i;//自身大小4默认对齐数1对齐数1char c2;//自身大小1默认对齐数1对齐数1
};
#pragma pack()//将默认对齐数修改为8
int main()
{printf(%zd\n, sizeof(struct S));//6return 0;
}三.结构体实现位段
结构体有实现位段的功能。
1.什么是位段
位段的声明和结构是类似的有两个不同
位段的成员必须是 int、unsigned int 或 signed int 在C99中位段成员的类型也可以选择其他类型。位段的成员名后边有一个冒号和一个数字。位段中的位二进制的位。
位段与结构体语法上的区别代码如下
#includestdio.h
struct A//结构体
{int a;int b;int c;int d;
};
struct B//结构体实现位段
{int a : 2;//只给了两个比特位意味着只能存放0,1,2,3,不能存放大于它们的值int b : 5;//同理int c : 10;int d : 30;
};
int main()
{printf(%zd\n, sizeof(struct A));//16个字节printf(%zd\n, sizeof(struct B));//8个字节//发现位段较于结构体节省了空间return 0;
}总结位段相较于结构体节省了空间。
2.位段的内存分配
位段的成员可以是 int unsigned int signed int 或者是 char 等类型。位段的空间上是按照需要以4个字节 int 或者1个字节 char 的方式来开辟的。位段涉及很多不确定因素位段是不跨平台的注重可移植的程序应该避免使用位段。
#includestdio.h
struct S
{char a : 3;char b : 4;char c : 5;char d : 4;
};
int main()
{struct S s { 0 };s.a 10;s.b 12;s.c 3;s.d 4;printf(%zd\n, sizeof(struct S));//3个字节return 0;
}1.给定了空间后在空间的内部是从左向右使用还是从右向左使用这个是不确定的。 假设从右向左使用。 2.当剩下的空间不足以存放下一个成员的时候空间是浪费还是使用这个是不确定的。 假设浪费。 3.位段的跨平台问题
int位段被当成有符号数还是无符号数是不确定的。位段中最大位的数目不能确定。16位机器最大16sizeof(int)232位机器最大32sizeof(int)4写成27在16位机器会出问题。位段中的成员在内存中从左向右分配还是从右向左分配标准尚未定义。当⼀个结构包含两个位段第⼆个位段成员比较大无法容纳于第⼀个位段剩余的位时是舍弃剩余的位还是利用这是不确定的。
总结跟结构体相比位段可以达到同样的效果并且可以很好的节省空间但是有跨平台的问题存在。
4.位段的应用
下图是网络协议中IP数据报的格式我们可以看到其中很多的属性只需要几个bit位就能描述这里使用位段能够实现想要的效果也节省了空间这样网络传输的数据报大小也会较小⼀些对网络的畅通是有帮助的。
在网络中发送数据的时候需要进行数据的封装例如加上源地址与目的地址。计算机网络中的网络层协议—— IP协议。为了避免网络拥堵相办法节省空间使用的就是位段。
5.位段使用的注意事项
位段的几个成员共有同⼀个字节这样有些成员的起始位置并不是某个字节的起始位置那么这些位置处是没有地址的。内存中每个字节分配一个地址一个字节内部的bit位是没有地址的。所以不能对位段的成员使用操作符这样就不能使用 scanf 直接给位段的成员输⼊值只能是先输⼊放在一个变量中然后赋值给位段的成员。
#includestdio.h
struct A
{int _a : 2;int _b : 5;int _c : 10;int _d : 30;
};
int main()
{//这是错误的struct A sa { 0 };scanf(%d, sa._b); //正确的示范int b 0;scanf(%d, b);sa._b b;return 0;
}创作不易如果能帮到你的话能赏个三连吗感谢啦