ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

一篇文章,了解自定义类型:结构体

一篇文章,了解自定义类型:结构体 目录结构体类型的声明结构的特殊声明结构的自引用结构体的内存对齐对齐规则为什么存在内存对齐修改默认对齐数结构体的传参结构体实现位段什么是位段位段的内存分配位段的跨平台问题位段的应用位段使用的注意事项结构体类型的声明结构是一些值的集合每个值叫作成员变量成员变量可以是不同类型的。structtag{member-list};variable-list例如structstudent{charname[20];intage;charsex[20];charid[20];};// -注意分号结构的特殊声明在声明结构时可以进行不完全声明。//匿名结构体类型struct{charname[20];charsex[20];}x;//绑定在了变量x上这种类型就是匿名结构体类型。除非进行重命名不然该结构体类型只能用一次绑定声明的结构体变量上别人用不了结构的自引用在结构中包含一个类型为自己的成员地址就是结构的自引用。structNode{charname[20];structNode*next;};结构体的内存对齐当我们想要计算结构体的内存大小时就必须要知道结构体的内存大小规则——内存对齐对齐规则offset函数可以测量结构体中成员的偏移量结构体的第 1 个成员对齐到和结构体变量起始位置偏移量为 0的地址处。从第 2 个成员变量开始都要对齐到某个对齐数的整数倍的地址处。对齐数 编译器默认的一个对齐数与 该成员变量大小的较小值。VS 中默认的值为 8Linux 中 gcc 没有默认对齐数对齐数就是成员自身的大小结构体总大小为最大对齐数结构体中每个成员变量都有一个对齐数所有对齐数中最大的的整数倍。如果嵌套了结构体的情况嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处结构体的整体大小就是所有最大对齐数含嵌套结构体中成员的对齐数的整数倍。为什么存在内存对齐内存对齐这么浪费空间还要搞一套规则存在的意义是什么呢平台原因移植原因不是所有的硬件平台都能访问任意地址上的任意数据某些硬件平台只能在某些地址处取某些特定类型的数据否则抛出硬件异常。性能原因数据结构尤其是栈应该尽可能地在自然边界上对齐。原因在于为了访问未对齐的内存处理器需要作两次内存访问而对齐的内存访问仅需要一次访问。假设一个处理器总是从内存中取 8 个字节则地址必须是 8 的倍数。如果我们能保证将所有的 double 类型数据的地址都对齐成 8 的倍数那么就可以用一个内存操作来读或者写值了。否则我们可能需要执行两次内存访问因为对象可能被分放在两个 8 字节内存块中。总体来说结构体的内存对齐是拿空间来换取时间的做法。由对齐规则可知structs{inta;charb;charc;};//占8个字节大小structs{charb;inta;charc;};//占12个字节大小仅仅只是排序上的差异结构体的内存大小就发生了变化造成了不同程度的空间浪费。所以在结构体成员顺序可以调整前提下如果既要考虑节省空间又要考虑对齐那就让占用空间小的成员尽量集中在一起。修改默认对齐数如果想更改默认对齐数以达到节省空间的目的可以有如下指令#pragmapack(1)//这里就把默认对齐数设置成了1建议修改为2^2数#pragmapack()//取消了设置这里就恢复成了编译器默认的对齐数结构体的传参当结构体与函数结合就涉及了结构体的传参这里有传值调用和传址调用两种形式。传值调用#includestdio.hstructs{intarr[5];inta;};voidprint(structst){for(inti0;i5;i){printf(%d ,t.arr[i]);}printf(%d ,t.a);}intmain(){structsx{{1,2,3,4,5},10};print(x);return0;}传址调用#includestdio.hstructs{intarr[5];inta;};voidprint(conststructs*t){for(inti0;i5;i){printf(%d ,*t-arr[i]);}printf(%d ,*t-a);}intmain(){structsx{{1,2,3,4,5},10};print(structsx);return0;}建议优先传址调用原因函数传参时参数需要压栈这会带来时间和空间上的系统开销。如果传递一个较大的结构体对象压栈的系统开销会更大从而导致性能下降。结论结构体传参的时候要传结构体的地址结构体实现位段什么是位段位段的声明与结构体类似但存在两点不同位段的成员必须是 int、unsigned int 或 signed int在 C99 中位段成员的类型也可以选择其他整型家族类型比如 char。位段的成员名后边有一个冒号和一个数字。位段的内存分配位段的成员可以是 int、unsigned int、signed int 或者是 char 等类型。位段的空间是按照需要以 4 个字节int或者 1 个字节char的方式来开辟的。位段涉及很多不确定因素位段是不跨平台的注重可移植的程序应该避免使用位段。structS{chara:3;charb:4;charc:5;chard:4;};intmain(){structSs{0};s.a10;s.b12;s.c3;s.d4;return0;}以下是开辟过程位段的跨平台问题int 位段被当成有符号数还是无符号数是不确定的。位段中最大位的数目不能确定。16 位机器最大 1632 位机器最大 32写成 27在 16 位机器会出问题。位段中的成员在内存中从左向右分配还是从右向左分配标准尚未定义。当一个结构包含两个位段第二个位段成员比较大无法容纳于第一个位段剩余的位时是舍弃剩余的位还是利用这是不确定的。总结跟结构相比位段可以达到同样的效果并且可以很好的节省空间但是有跨平台的问题存在。位段的应用下图是网络协议中 IP 数据报的格式我们可以看到其中很多的属性只需要几个 bit 位就能描述这里使用位段能够实现想要的效果也节省了空间这样网络传输的数据报大小也会较小一些对网络的畅通是有帮助的。位段使用的注意事项当出现位段的几个成员共用一个字节时这几个成员地址是不存在的因为一个字节才有一个地址。因此在涉及类似scanf 操作时不可以直接对成员取地址。我们必须创建一个变量进行scan 操作进而赋值给位段成员。#includestdio.hstructS{inta:5;intb:4;};/*int main() { stuct S s {0}; scanf(%d,s.a};//错误的写法 return 0; }*/intmain(){stuct S s{0};intc0;scanf(%d,c};s.ac;//赋值的方法return0;}
返回列表