零长度数组
零长度数组、变长数组都是GNU C编译器支持的数组类型。
什么是零长度数组?
首先肯定长度是为0的数组
ANSI C规定定义一个数组长度必须为一个常数,那么就是这个数组的长度在编译的时候就确定了。
int a[10];
但是在C99标准中规定可以定义一个变长的数组。
int len;
int a[len]
这样可以让我们的数组在运行的时候再确定,这是变长。
GUN C编译器支持这样定义
int a[0];
零长度数组不占用内存存储空间。可以用sizeog进行测试哦。
零长度一般很少单独使用,常常作为结构体的一个成员,构成一个变长结构体。
struct buffer{
int len;
int a[0];
};
这个buffer的大小是4.
下面来展示一下这个玩意怎么用的诶?
首先的用处在于通过零长度数组去访问结构体的内存,但是还不占内存。
闲话少说,给大爷们上代码。
struct buffer{
int len;
int a[0];
};
struct buffer *buf;
buf = (struct buffer *)malloc(sizeof(struct buffer)+20);
buf->len = 20;
strcpy(buf->a,"hello zhaixue.cc!\n");
puts(buf->a)//打印到屏幕
free(buf);//配套释放内存
return 0;
使用malloc申请一片内存,大小为sizeof(buffer)+20,即24字节。其中4字节用来表示内存的长度20,剩下的20字节空间,可以通过结构体成员a直接访问这片内存。这里不就把一个字符串放进去了。
内核中的零长度数组
前面说了零长度数组在内核中一般以变长结构体的形式出现。现在看看实际内核是怎么用这个的。来看个内核中USB驱动的应用。
网卡驱动中我们都知道有个玩意叫套接字:socket buffer,这个用来传输数据包的
同样在USB驱动中,也有个类似的叫做URB:USB Request Block,即USB请求块,用来传输USB数据包。它的结构体长这个样子:
这个结构体内定义了USB数据包的传输方向、传输地址、传输大小、传输模式等。这些细节我们不深究,只看最后一个成员。
struct usb_iso_packet_descriptor iso_fream_desc[0];
这个是干什么的呢?主要用于USB的同步传输。
USB有4种传输模式:中断传输、控制传输、批量传输和同步传输。不同的USB设备对传输速度、传输数据安全性的要求不同,所采用的传输模式也不同。
USB摄像头对视频或图像的传输实时性要求较高,对数据的丢帧不是很在意,丢一帧无所谓,接着往下传就可以了。所以USB摄像头采用的是USB同步传输模式。
USB摄像头一般会支持多种分辨率,从16*16到高清720P多种格式。不同分辨率的视频传输,一帧图像数据的大小是不一样的,对USB传输数据包的大小和个数需求是不一样的。那么USB到底该如何设计,才能在不影响USB其他传输模式的前提下,适配这种不同大小的数据传输需求呢?
当用户设置不同分辨率的视频格式时,USB就使用不同大小和个数的数据包来传输一帧视频数据。通过零长度数组构成的这个变长结构体就可以满足这个要求。USB驱动可以根据一帧图像数据的大小,灵活地申请内存空间,以满足不同大小的数据传输。而且这个零长度数组又不占用结构体的存储空间。当USB使用其他模式传输时,不受任何影响,完全可以当这个零长度数组不存在。
牛吧!!!
不过吧有个想法关于指针的。
为啥不用指针呢?指针指向的内存也可变化啊。
首先说说这个事:数组名在作为函数参数进行参数传递时,就相当于一个指针。那数组名是指针吗?
数组名在作为参数传递时,传递的确实是一个地址,但数组名绝不是指针,两者不是同一个东西。
数组名用来表征一块连续内存空间的地址,而指针是一个变量,编译器要给它单独分配一个内存空间,用来存放它指向的变量的地址。
其实就是数组名是不能变化的,而指针相当于一个盒子,你可以往里面放东西,但是数组名它就个东西。
对于一个指针变量,编译器要为这个指针变量单独分配一个存储空间,然后在这个存储空间上存放另一个变量的地址,我们就说这个指针指向这个变量。
对于数组名,编译器不会再给它分配一个单独的存储空间,它仅仅是一个符号,和函数名一样,用来表示一个地址。
int array1[10] = {1,2,3,4,5,6,7,8};
int array2[0];
int *p = &array1[5]
交叉编译
反汇编
数组名array1仅仅表示这40个连续存储空间的首地址
指针变量p,编译器给它分配了0x2104c这个存储空间
array2的地址为0x21054,在BSS段的后面。array2符号表示的默认地址是一片未使用的内存空间,仅此而已,编译器绝不会单独再给其分配一个存储空间来存储数组名。(bbs段是未初始化的数据,不占用磁盘空间,是在程序执行前由内核初始化完成。)
因此零长度数组不会对结构体定义造成冗余,而且使用起来很方便。
- 参考资料:《嵌入式C语言自我修养:从芯片、编译器到操作系统》