- GCC 在标准的 C/C++ 基础上做了有实用性的扩展, 零长度数组(Arrays of Length Zero) 就是其中一个知名的扩展
1 what
struct Packet
{
int state;
int len;
char arr[0]; // 这里的0长结构体就为变长结构体提供了非常好的支持
};
- 用途:定义数据结构时非常有用,满足需要变长度的结构体,创建结构体对象时,可根据实际的需要指定这个可变
长数组的长度,并分配相应的空 - 用法如上:声明一个长度为0的数组,是的结构体是可变长的,对于编译器来说,长度为0的数组不占用空间,只是一个偏移量,数组名代表了一个不可修改的地址常量
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct buffer
{
int len;
char a[0];
};
int main(void)
{
struct buffer *buf;
buf = (struct buffer *)malloc(sizeof(struct buffer) + 20);
buf->len = 20;
strcpy(buf->a, "hello\\n"); // 再加一个\表示不是转义字符
puts(buf->a); //标准设备输出 hello\n
// puts 函数输出字符串自动换行输出
free(buf);
return 0;
}
- 我们使用 malloc 申请一片内存,大小为 sizeof(buffer) + 20,即24个字节大小。其中4个字节用来存储结构体指针 buf 指向的结构体类型变量,另外20个字节空间,才是我们真正使用的内存空间。我们可以通过结构体成员 a,直接访问这片内存
- 通过这种灵活的动态内存申请方式,这个 buffer 结构体表示的一片内存缓冲区,就可以随时调整,可大可小。这个特性,在一些场合非常有用。
- 比如,现在很多在线视频网站,都支持多种格式的视频播放:标清、高清、超清、蓝光甚至4K。如果我们本地程序需要在内存中申请一个 buffer 用来缓存解码后的视频数据,那么,不同的播放格式,需要的 buffer 大小是不一样的。 如果我们按照 4K 的标准去申请内存,那么当播放标清视频时,就用不了这么大的缓冲区,白白浪费内存。 而使用变长结构体,我们就可以根据用户的播放格式设置,灵活地申请不同大小的 buffer,大大节省了内存空间
2 why
问什么不用指针代替零长数组 ?
数组名在作为函数参数传递时,确实传递的是一个地址,但数组名绝不是指针,两者不是同一个东西。数组名用来表征一块连续内存存储空间的地址,是个常量,表示一个地址,而指针是一个变量,编译器要给它单独再分配一个内存空间,用来存放它指向的变量的地址
#include <stdio.h>
struct buffer1
{
int len;
int a[0];
};
struct buffer2
{
int len;
int *a;
} __attribute__((packed)); // 将结构体压实
int main(void)
{
printf("buffer1: %ld\n", sizeof(struct buffer1)); // buffer1: 4
printf("buffer2: %ld\n", sizeof(struct buffer2)); // buffer2: 12
return 0;
}
- 数组名在作为函数参数传递时,确实传递的是一个地址,但数组名绝不是指针,两者不是同一个东西。数组名用来表征一块连续内存存储空间的地址,而指针是一个变量,编译器要给它单独再分配一个内存空间,用来存放它指向的变量的地址
- 而数组名,编译器不会再给其分配一个存储空间的,它仅仅是一个符号,跟函数名一样,用来表示一个地址
而数组名,编译器不会再给其分配一个存储空间的#include <stdio.h>
#include <stdlib.h>
typedef struct student
{
int num; // 学号
char name[20]; // 姓名
char sex; // 性别
} student;
typedef struct class
{
char *teacher;
int class_id;
student students[];
} class;
int main()
{
class *classA;
classA = (class *)malloc(sizeof(class) + sizeof(student) * 5);
// 遍历学生赋值
for (int i = 0; i < 5; i++)
{
classA->students[i].num = 230580 + i;
printf("请输入学号为%d的姓名:\n", classA->students[i].num);
scanf("%s", classA->students[i].name);
}
// 遍历学生输出
for (int i = 0; i < 5; i++)
{
printf("学号%d的姓名是%s:\n", classA->students[i].num, classA->students[i].name);
}
// 释放资源
free(classA);
classA = NULL; // 置空
return 0;
}
用指针来写
#include <stdio.h>
#include <stdlib.h>
typedef struct student
{
int num; // 学号
char name[20]; // 姓名
char sex; // 性别
} student;
typedef struct class
{
char *teacher;
int class_id;
student *students;
} class;
int main()
{
class *classA;
classA = (class *)malloc(sizeof(class));
classA->students = (student *)malloc(sizeof(student) * 5);
// 遍历学生赋值
for (int i = 0; i < 5; i++)
{
classA->students[i].num = 230580 + i;
printf("请输入学号为%d的姓名:\n", classA->students[i].num);
scanf("%s", classA->students[i].name);
}
// 遍历学生输出
for (int i = 0; i < 5; i++)
{
printf("学号%d的姓名是%s:\n", classA->students[i].num, classA->students[i].name);
}
// 释放资源
free(classA->students);
classA->students = NULL;
free(classA);
classA = NULL;
return 0;
}
- 零长数组一般和结构体搭配使用,其比起在结构体中声明一个指针变量、再进行动态分配的效率要高。因为在访问数组内容时,不需要间接访问,避免了两次访存。
- 同时因为零长数组表示数据内容时,其数据空间是动态分配的,所以比静态分配内存要灵活。