• C语言0长度数组(可变数组/柔性数组)


    一、零长度数组概念

      众所周知, GNU/GCC 在标准的 C/C++ 基础上做了有实用性的扩展, 零长度数组(Arrays of Length Zero) 就是其中一个知名的扩展,多数情况下, 其应用在变长数组中, 其定义如下

    struct Packet
    {
        int state;
        int len;
        char cData[0]; //这里的0长结构体就为变长结构体提供了非常好的支持
    };

    首先对 0长度数组, 也叫柔性数组 做一个解释 :

    (1)用途 : 长度为0的数组的主要用途是为了满足需要变长度的结构体

    (2)用法 : 在一个结构体的最后, 申明一个长度为0的数组, 就可以使得这个结构体是可变长的. 对于编译器来说, 此时长度为0的数组并不占用空间, 因为数组名本身不占空间, 它只是一个偏移量, 数组名这个符号本身代表了一个不可修改的地址常量.

    二、 0长度数组的用途

      我们设想这样一个场景, 我们在网络通信过程中使用的数据缓冲区, 缓冲区包括一个len字段和data字段, 分别标识数据的长度和传输的数据, 我们常见的有几种设计思路。

    • 定长数据缓冲区, 设置一个足够大小 MAX_LENGTH 的数据缓冲区设置一个指向实际数据的指针, 每次使用时, 按照数据的长度动态的开辟数据缓冲区的空间.

    • 设置一个指向实际数据的指针, 每次使用时, 按照数据的长度动态的开辟数据缓冲区的空间.

      我们从实际场景中应用的设计来考虑他们的优劣. 主要考虑的有, 缓冲区空间的开辟, 释放和访问。

     1 定长包(开辟空间, 释放, 访问)

      比如我要发送 1024 字节的数据, 如果用定长包, 假设定长包的长度 MAX_LENGTH 为 2048, 就会浪费 1024 个字节的空间, 也会造成不必要的流量浪费.

      (1)数据结构定义

    //  定长缓冲区
    struct max_buffer
    {
        int     len;
        char    data[MAX_LENGTH];
    };

      (2)数据结构大小

        考虑对齐, 那么数据结构的大小 >= sizeof(int) + sizeof(char) * MAX_LENGTH

    由于考虑到数据的溢出, 变长数据包中的 data 数组长度一般会设置得足够长足以容纳最大的数据, 因此 max_buffer 中的 data 数组很多情况下都没有填满数据, 因此造成了浪费。

      (3)数据包的构造

        假如我们要发送 CURR_LENGTH = 1024 个字节, 我们如何构造这个数据包呢,一般来说, 我们会返回一个指向缓冲区数据结构 max_buffer 的指针.

     ///  开辟
        if ((mbuffer = (struct max_buffer *)malloc(sizeof(struct max_buffer))) != NULL)
        {
            mbuffer->len = CURR_LENGTH;
            memcpy(mbuffer->data, "Hello World", CURR_LENGTH);
    
    
            printf("%d, %s
    ", mbuffer->len, mbuffer->data);
        }

      (4)访问

       这段内存要分两部分使用,

      前部分 4 个字节 p->len, 作为包头(就是多出来的那部分),这个包头是用来描述紧接着包头后面的数据部分的长度,这里是 1024, 所以前四个字节赋值为 1024 (既然我们要构造不定长数据包,那么这个包到底有多长呢,因此,我们就必须通过一个变量来表明这个数据包的长度,这就是len的作用),

    而紧接其后的内存是真正的数据部分, 通过 p->data, 最后, 进行一个 memcpy() 内存拷贝, 把要发送的数据填入到这段内存当中

      (5)释放

        那么当使用完毕释放数据的空间的时候, 直接释放就可以了。

     /// 销毁
        free(mbuffer);
        mbuffer = NULL;

      (6)小结

    1. 使用定长数组, 作为数据缓冲区, 为了避免造成缓冲区溢出, 数组的大小一般设为足够的空间 MAX_LENGTH, 而实际使用过程中, 达到 MAX_LENGTH 长度的数据很少, 那么多数情况下, 缓冲区的大部分空间都是浪费掉的.

    2. 但是使用过程很简单, 数据空间的开辟和释放简单, 无需程序员考虑额外的操作

    2 指针数据包(开辟空间, 释放, 访问)

      如果你将上面的长度为 MAX_LENGTH 的定长数组换为指针, 每次使用时动态的开辟 CURR_LENGTH 大小的空间, 那么就避免造成 MAX_LENGTH - CURR_LENGTH 空间的浪费, 只浪费了一个指针域的空间.

      (1)数据包定义

    struct point_buffer
    {
        int     len;
        char    *data;
    };

      (2)数据结构大小

        考虑对齐, 那么数据结构的大小 >= sizeof(int) + sizeof(char *)

      (3)空间分配

        但是也造成了使用在分配内存时,需采用两步

    // =====================
        // 指针数组  占用-开辟-销毁
        // =====================
        ///  占用
        printf("the length of struct test3:%d
    ",sizeof(struct point_buffer));
        ///  开辟
        if ((pbuffer = (struct point_buffer *)malloc(sizeof(struct point_buffer))) != NULL)
        {
            pbuffer->len = CURR_LENGTH;
            if ((pbuffer->data = (char *)malloc(sizeof(char) * CURR_LENGTH)) != NULL)
            {
                memcpy(pbuffer->data, "Hello World", CURR_LENGTH);
    
    
                printf("%d, %s
    ", pbuffer->len, pbuffer->data);
            }
        }
    1. 首先, 需为结构体分配一块内存空间.其次再为结构体中的成员变量分配内存空间.其次再为结构体中的成员变量分配内存空间.

    2. 其次再为结构体中的成员变量分配内存空间.

    这样两次分配的内存是不连续的, 需要分别对其进行管理. 当使用长度为的数组时, 则是采用一次分配的原则, 一次性将所需的内存全部分配给它.

      (4)释放

        相反, 释放时也是一样的.

     /// 销毁
        free(pbuffer->data);
        free(pbuffer);
        pbuffer = NULL;

      (5)小结

    1. 使用指针结果作为缓冲区, 只多使用了一个指针大小的空间, 无需使用 MAX_LENGTH 长度的数组, 不会造成空间的大量浪费.

        2. 但那是开辟空间时, 需要额外开辟数据域的空间, 施放时候也需要显示释放数据域的空间, 但是实际使用过程中, 往往在函数中开辟空间, 然后返回给使用者指向 struct point_buffer 的指针, 这时候我们并不能假定使用者了解我们开辟的细节, 并按照约定的操作释放空间, 因此使用起来多有不便, 甚至造成内存泄漏。

    3  变长数据缓冲区(开辟空间, 释放, 访问)

      定长数组使用方便, 但是却浪费空间, 指针形式只多使用了一个指针的空间, 不会造成大量空间分浪费, 但是使用起来需要多次分配, 多次释放, 那么有没有一种实现方式能够既不浪费空间, 又使用方便的呢?

    GNU C 的0长度数组, 也叫变长数组, 柔性数组就是这样一个扩展. 对于0长数组的这个特点,很容易构造出变成结构体,如缓冲区,数据包等等:

      (1)数据结构定义

    //  0长度数组
    struct zero_buffer
    {
        int     len;
        char    data[0];
    };

      (2)数据结构大小

         这样的变长数组常用于网络通信中构造不定长数据包, 不会浪费空间浪费网络流量, 因为char data[0]; 只是个数组名, 是不占用存储空间的,

      即 sizeof(struct zero_buffer) = sizeof(int)

      (3)开辟空间

        那么我们使用的时候, 只需要开辟一次空间即可

    ///  开辟
        if ((zbuffer = (struct zero_buffer *)malloc(sizeof(struct zero_buffer) + sizeof(char) * CURR_LENGTH)) != NULL)
        {
            zbuffer->len = CURR_LENGTH;
            memcpy(zbuffer->data, "Hello World", CURR_LENGTH);
    
    
            printf("%d, %s
    ", zbuffer->len, zbuffer->data);
        }

      (4)释放空间

        释放空间也是一样的, 一次释放即可

     ///  销毁
        free(zbuffer);
        zbuffer = NULL;

    总结:

    • 长度为0的数组并不占有内存空间, 而指针方式需要占用内存空间.

    • 对于长度为0数组, 在申请内存空间时, 采用一次性分配的原则进行; 对于包含指针的结构体, 才申请空间时需分别进行, 释放时也需分别释放.

    • 对于长度为的数组的访问可采用数组方式进行

    111
  • 相关阅读:
    todo: SDC
    2017-2018-2 实验四《Android程序设计》实验报告
    《Java程序设计》第十周课下作业
    2017-2018-2 实验三 《Java面向对象程序设计》实验报告
    《Java程序设计》第九周学习总结
    结对编程练习_四则运算(第二周)
    《Java程序设计》第八周学习总结
    2017-2018-2 实验二 《Java面向对象程序设计》实验报告
    结对编程练习_四则运算(第一周)
    《Java程序设计》第七周学习总结
  • 原文地址:https://www.cnblogs.com/zwj-199306231519/p/15334605.html
Copyright © 2020-2023  润新知