长度为0的数组

本文为转载,原地址为 https://blog.youkuaiyun.com/gatieme/article/details/64131322

1 零长度数组概念
众所周知, GNU/GCC 在标准的 C/C++ 基础上做了有实用性的扩展, 零长度数组(Arrays of Length Zero) 就是其中一个知名的扩展.

多数情况下, 其应用在变长数组中, 其定义如下

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


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

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

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

(注意 : 数组名永远都不会是指针!), 但对于这个数组的大小, 我们可以进行动态分配

注意 :如果结构体是通过calloc、malloc或 者new等动态分配方式生成,在不需要时要释放相应的空间。

优点 :比起在结构体中声明一个指针变量、再进行动态分 配的办法,这种方法效率要高。因为在访问数组内容时,不需要间接访问,避免了两次访存。

缺点 :在结构体中,数组为0的数组必须在最后声明,使 用上有一定限制。

对于编译器而言, 数组名仅仅是一个符号, 它不会占用任何空间, 它在结构体中, 只是代表了一个偏移量, 代表一个不可修改的地址常量!

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

定长数据缓冲区, 设置一个足够大小 MAX_LENGTH 的数据缓冲区

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

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

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

数据结构定义
//  定长缓冲区
struct max_buffer
{
    int     len;
    char    data[MAX_LENGTH];
};


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

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

数据包的构造
假如我们要发送 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\n", mbuffer->len, mbuffer->data);
    }


访问
这段内存要分两部分使用

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

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

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

    /// 销毁
    free(mbuffer);
    mbuffer = NULL;
1
2
3
小结

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

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

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

数据包定义
struct point_buffer
{
    int     len;
    char    *data;
};


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

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

    // =====================
    // 指针数组  占用-开辟-销毁
    // =====================
    ///  占用
    printf("the length of struct test3:%d\n",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\n", pbuffer->len, pbuffer->data);
        }
    }



首先, 需为结构体分配一块内存空间;

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

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

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

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


小结

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

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

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

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

数据结构定义

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


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

即 sizeof(struct zero_buffer) = sizeof(int)

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

   ///  开辟
    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\n", zbuffer->len, zbuffer->data);
    }



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

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


2.4 总结
 

// zero_length_array.c
#include <stdio.h>
#include <stdlib.h>


#define MAX_LENGTH      1024
#define CURR_LENGTH      512

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


//  定长数组
struct max_buffer
{
    int     len;
    char    data[MAX_LENGTH];
}__attribute((packed));


//  指针数组
struct point_buffer
{
    int     len;
    char    *data;
}__attribute((packed));

int main(void)
{
    struct zero_buffer  *zbuffer = NULL;
    struct max_buffer   *mbuffer = NULL;
    struct point_buffer *pbuffer = NULL;


    // =====================
    // 0长度数组  占用-开辟-销毁
    // =====================
    ///  占用
    printf("the length of struct test1:%d\n",sizeof(struct zero_buffer));
    ///  开辟
    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\n", zbuffer->len, zbuffer->data);
    }
    ///  销毁
    free(zbuffer);
    zbuffer = NULL;


    // =====================
    // 定长数组  占用-开辟-销毁
    // =====================
    ///  占用
    printf("the length of struct test2:%d\n",sizeof(struct 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\n", mbuffer->len, mbuffer->data);
    }
    /// 销毁
    free(mbuffer);
    mbuffer = NULL;

    // =====================
    // 指针数组  占用-开辟-销毁
    // =====================
    ///  占用
    printf("the length of struct test3:%d\n",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\n", pbuffer->len, pbuffer->data);
        }
    }
    /// 销毁
    free(pbuffer->data);
    free(pbuffer);
    pbuffer = NULL;


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

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

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

 

<think>我们正在讨论数组长度的问题。用户的问题是:数组能否定义长度0? 根据C/C++语言规范,数组可以定义长度0,但这是非标准扩展。在标准C/C++中,数组大小必须至少为1。然而,某些编译器(如GCC)允许零长度数组作为扩展,通常用于动态大小的结构体(即结构体的最后一个成员可以是零长度数组,用于动态分配)。 但是,在大多数情况下,定义长度0数组并不常见,而且可能引起问题。例如,在标准C++中,定义一个长度0数组是未定义行为(UB),编译器可能会报错。 另外,在用户提供的引用中,他们遇到了关于数组长度计算的问题和数组传递的问题,但并没有直接讨论零长度数组。不过,我们可以从这些引用中看到,数组在函数传递时会退化为指针,因此在函数内部无法通过sizeof获取数组长度。如果数组长度0,那么在主函数中使用sizeof(arr)/sizeof(arr[0])会得到0(因为sizeof(arr)为0,除以一个元素大小(比如4)还是0),但这种情况在标准C++中是不允许的。 结论:在标准C/C++中,定义长度0数组是不允许的。但在某些编译器中作为扩展允许,使用时需谨慎。 同时,用户引用[2]提到了Uniapp中判断数组长度时出现的错误,这是一个JavaScript环境的问题,错误是因为尝试读取一个未定义变量的length属性。在JavaScript中,可以定义空数组长度0),但不能对undefined使用length属性。 因此,我们需要区分语言环境: 1. 在C/C++中,标准不允许零长度数组(除了某些特定的编译器扩展)。 2. 在JavaScript(或Uniapp使用的Vue框架)中,数组可以定义长度0,但要注意数组变量必须已定义(不能是undefined)。 下面详细说明: </think> 在编程中,数组能否定义长度0取决于具体语言和规范: ### 1. **标准C/C++(不允许)** - 在标准C/C++中,数组长度必须至少为1,定义长度0数组是**未定义行为**。 - 尝试定义零长度数组会触发编译错误(如GCC的`error: zero size array`): ```c int arr[0]; // 编译报错 ``` ### 2. **编译器扩展(有限支持)** 部分编译器(如GCC/Clang)提供扩展支持零长度数组,但需注意: - **仅允许作为结构体的最后一个成员**(柔性数组): ```c struct flex_array { int size; int data[0]; // 柔性数组,不占实际空间 }; ``` - 需手动分配内存:`malloc(sizeof(struct flex_array) + n * sizeof(int))`。 - **禁止独立定义**:`int arr[0];` 即使支持也易引发内存错误[^1]。 ### 3. **其他语言(通常允许)** - **Java/JavaScript/Python**:明确支持空数组长度=0),是安全行为: ```javascript let arr = []; // JavaScript空数组 (arr.length === 0) ``` ```java int[] arr = new int[0]; // Java空数组 ``` - **Uniapp/前端框架**:空数组常见,但需避免操作`undefined`变量(如引用[2]的错误)[^2]。 ### 关键区别总结: | 场景 | 是否允许长度=0 | 说明 | |--------------------|----------------|--------------------------| | 标准C/C++ | ❌ 禁止 | 编译报错 | | C/C++编译器扩展 | ⚠️ 受限 | 仅支持结构体柔性数组 | | Java/JavaScript等 | ✅ 允许 | 空数组是合法对象 | | Uniapp/Vue框架 | ✅ 允许 | 需确保变量已初始化 | > 最佳实践:在C/C++中避免零长度数组;其他语言可安全使用空数组,但需初始化变量防止`undefined`错误。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值