• C语言10、自定义类型:结构体进阶,位段


    结构体

    结构体的声明

    1. 普通声明
    2. struct tag
    3. {
    4. member-list;
    5. }variable-list;
    6. 特殊声明
    7. 匿名结构体类型
    8. struct
    9. {
    10. int a;
    11. char b;
    12. float c;
    13. }x;
    14. struct
    15. {
    16. int a;
    17. char b;
    18. float c;
    19. }a[20], *p;
    20. 在上面代码的基础上,下面的代码合法吗?
    21. p = &x;
    22. 编译器会把上面的两个声明当成完全不同的两个类型。
    23. 所以是非法的

    特殊的声明

    在声明结构的时候,可以不完全声明

    比如:

    // 匿名结构体类型
    struct
    {
            int a ;
            char b ;
            float c ;
    } x ;
    struct
    {
            int a ;
            char b ;
            float c ;
    } a [ 20 ], * p ;
    上面的两个结构在声明的时候省略掉了结构体标签( tag )。
    那么问题来了?
    // 在上面代码的基础上,下面的代码合法吗?
    p = & x ;
    警告:
    编译器会把上面的两个声明当成完全不同的两个类型。
    所以是非法的

    结构的自引用

    在结构体中包含一个类型为该结构本身的成员是否可以呢?

    // 代码 1
    struct Node
    {
    int data ;
    struct Node next ;
    };
    // 可行否?
    如果可以,那 sizeof ( struct Node ) 是多少?
    不可以,正确的自引用方式:
    //代码 2
    struct Node
    {
    int data ;
    struct Node * next ;
    };

    注意:

    //代码3

    typedef struct
    {
    int data ;
    Node * next ;
    } Node ;
    // 这样写代码,可行否?
    // 解决方案:
    typedef struct Node
    {
    int data ;
    struct Node * next ;
    } Node ;

    结构体内存对齐

    这是一个特别热门的考点:结构体内存对齐

    如何计算?

    结构体的对齐规则:

            1.第一个成员在与结构体变量偏移量为0的地址处。

            2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处

            对齐数 = 编译器默认的一个对齐数与该成员大小的较小值

            注:vs中默认对齐数为8,若想改变对齐数,可使用#pragma pack()这个预处理指令修改,括号里写想设置的默认对齐数,如果单独#pragma,则是取消已经设置的默认对齐数,还原默认。Linux环境下没有默认对齐数,这时,它自身大小就是对齐数

            3.结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍

            4.如果是嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

    为什么存在内存对齐?有一些观点是这么说的:

    1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常

    2.性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要做两次内存访问;而对齐的内存访问仅需要一次访问

    总的来说就是:结构体的内存对齐是拿空间换时间的做法

    那么在设计结构体的时候,为了尽量满足对齐+节省空间,我们:

    让占用空间小的成员尽量集中在一起

    计算结构体大小(offsetof)

    size_t offsetof( structName, memberName );

    offsetof是一个宏函数,可以计算出结构体中某变量相对于首地址的偏移

    需要包含的头文件:#include

    printf("%u\n",offsetof(struct  s1,    c1) );

    位段

    位段的声明和结构是类似的,有两个不同:
    1.位段的成员必须是 int、unsigned int 或signed int 。
    2.位段的成员名后边有一个冒号和一个数字。
    位段不对齐,因为设计它就是为了节省空间

    比如:

    struct A
    {
            int _a : 2 ;//00
            int _b : 5 ;//00000
            int _c : 10 ;//00 0000 0000
            int _d : 30 ;//00 0000 0000 0000 0000 0000 0000 0000
    };
    位段的位实际指的是二进制位,冒号后面的数字的意义是其所占内存的bit位。_a这个成员只占2个bit位,它组成的结构体所占的空间可能会小一点,可以节省空间

    位段的内存分配

    注意,这里位段的存储顺序和大小端无关,因为大小端是排字节序的,而这里存储的是一个字节为单位,内部比特位的存储,而不是字节的顺序

    1. 位段的成员可以是 int、unsigned int、signed int 或者是 char (属于整形家族)类型
    2. 位段的空间上是按照需要以 4 个字节( int )或者 1 个字节( char )的方式来开辟的。
    3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段
    // 一个例子
    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 ;
    // 空间是如何开辟的?

     位段的跨平台问题

    1.int位段被当成有符号数还是无符号数是不确定的

    2.位段中最大位的数目不能确定(16位机器最大16,32位机器最大32,写成27的话,在16会机器上会出问题)

    3.位段中的成员在内存中从左向右分配,还是从右向左分配没有标准来定义

    4.当一个结构包含两个位段,第二个位段成员比较大,无法容纳第一个位段剩余的位时,是舍弃剩余的位还是利用剩余的位,是不确定的

    位段的应用场景

    在某些情况下,会涉及到位段。数据在网络上传输时,会有数据的封装,注意,下图的源ip地址指的是发送数据(A)的ip地址,目的ip地址是接受数据(B)的ip地址

    在网络协议栈会有下图这些,这些成员就能用位段的形式实现,本身空间就小,又节省了空间

  • 相关阅读:
    [附源码]java毕业设计演唱会售票系统
    Docker Swarm总结
    正点原子IMX6ULL驱动开发复盘
    c#学习_第三弹
    使用springboot框架读取和导入excel表格
    基于ADS的marx雪崩电路设计-设计实践(射频脉冲源)
    ROCKET PROPULSION ELEMENTS——CLASSIFICATION笔记
    linux 内核中的pid和前缀树
    nginx初识
    Linux nohup 命令
  • 原文地址:https://blog.csdn.net/weixin_60320290/article/details/126157159