【嵌入式】C语言高级编程-长度为0的数组(05)
00. 目录
文章目录
- 00. 目录
- 01. 什么是零长度数组
- 02. 零长度数组应用
- 03. 内核中的零长度数组
- 04. 指针可以代替零长度数组?
- 05. 附录
01. 什么是零长度数组
零长度数组就是长度为0的数组。
ANSI C 标准规定:定义一个数组时,数组的长度必须是一个常数,即数组的长度在编译的时候是确定的。在ANSI C 中定义一个数组的方法如下:
类型 数组名[数组元素个数];int array[10];
C99 新标准规定:可以定义一个变长数组。
int len;
scanf("%d", &len);
int array[len];
也就是说,数组的长度在编译时是未确定的,在程序运行的时候才确定,甚至可以由用户指定大小。比如,我们可以定义一个数组,然后在程序运行时才指定这个数组的大小,还可以通过输入数据来初始化数组。
程序示例
#include <stdio.h>int main(void)
{int len;int i = 0;printf("please input a length: ");scanf("%d", &len);int a[len];for (i = 0; i < len; i++){a[i] = i + 1;}for (i = 0; i < len; i++){printf("a[%d] = %d\n", i, a[i]);}return 0;
}
执行结果
deng@itcast:~/tmp$ gcc 7.c
deng@itcast:~/tmp$ ./a.out
please input a length: 10
a[0] = 1
a[1] = 2
a[2] = 3
a[3] = 4
a[4] = 5
a[5] = 6
a[6] = 7
a[7] = 8
a[8] = 9
a[9] = 10
在这个程序中,我们定义一个变量 len,作为数组的长度。程序运行后,我们可以通过输入指定数组的长度并初始化,最后再将数组的元素输出来。
我们在程序中定义一个零长度数组,你会发现除了 GCC 编译器,在其它编译环境下可能就编译通不过或者有警告信息。零长度数组的定义如下:
#include <stdio.h>int main(void)
{//定义长度为零的数组int a[0];return 0;
}
零长度数组有一个奇特的地方,就是它不占用内存存储空间。我们使用 sizeof 关键字来查看一下零长度数组在内存中所占存储空间的大小。
程序示例
#include <stdio.h>int main(void)
{int a[0];printf("sizeof(a): %lu\n", sizeof(a));return 0;
}
执行结果
deng@itcast:~/tmp$ gcc 7.c
deng@itcast:~/tmp$ ./a.out
sizeof(a): 0
我们定义一个零长度数组,使用 sizeof 查看其大小可以看到:零长度数组在内存中不占用空间,大小为0。
零长度数组一般单独使用的机会很少,它常常作为结构体的一个成员,构成一个变长结构体。
程序示例
#include <stdio.h>struct student
{int id;char sex;int a[0];
};int main(void)
{int a[0];printf("sizeof(struct): %lu\n", sizeof(struct student));return 0;
}
执行结果
deng@itcast:~/tmp$ gcc 7.c
deng@itcast:~/tmp$ ./a.out
sizeof(struct): 8
零长度数组在结构体中同样不占用存储空间,所以 student结构体的大小为8。
02. 零长度数组应用
零长度数组经常以变长结构体的形式,在某些特殊的应用场合,被程序员使用。在一个变长结构体中,零长度数组不占用结构体的存储空间,但是我们可以通过使用结构体的成员 a 去访问内存,非常方便。
程序示例
#include <stdio.h>
#include <string.h>
#include <stdlib.h>struct student
{int id;char sex;char a[0];
};int main(void)
{struct student *s = NULL;s = malloc(sizeof(struct student) + 20);if (NULL == s){printf("malloc failed..\n");return 1;}memset(s, 0, sizeof(struct student) + 20);s->id = 1;s->sex = 'M';strcpy(s->a, "hello world");printf("id: %d sex: %c a: %s\n", s->id, s->sex, s->a);free(s);return 0;
}
执行结果
deng@itcast:~/tmp$ gcc 7.c
deng@itcast:~/tmp$ ./a.out
id: 1 sex: M a: hello world
在这个程序中,我们使用 malloc 申请一片内存,大小为 sizeof(buffer) + 20,即28个字节大小。其中8个字节用来存储结构体指针 student 指向的结构体类型变量,另外20个字节空间,才是我们真正使用的内存空间。我们可以通过结构体成员 a,直接访问这片内存。
通过这种灵活的动态内存申请方式,这个 student结构体表示的一片内存缓冲区,就可以随时调整,可大可小。这个特性,在一些场合非常有用。比如,现在很多在线视频网站,都支持多种格式的视频播放:普清、高清、超清、1080P、蓝光甚至4K。如果我们本地程序需要在内存中申请一个 buffer 用来缓存解码后的视频数据,那么,不同的播放格式,需要的 buffer 大小是不一样的。如果我们按照 4K 的标准去申请内存,那么当播放普清视频时,就用不了这么大的缓冲区,白白浪费内存。而使用变长结构体,我们就可以根据用户的播放格式设置,灵活地申请不同大小的 buffer,大大节省了内存空间。
03. 内核中的零长度数组
零长度数组在内核中,一般以变长结构体的形式使用。今天我们就分析一下 Linux 内核中的 USB 驱动。在网卡驱动中,大家可能都比较熟悉一个名字:套接字缓冲区,即 socket buffer,用来传输网络数据包。同样,在 USB 驱动中,也有一个类似的东西,叫 URB,其全名为 USB request block,即 USB 请求块,用来传输 USB 数据包。
linux-headers-5.4.0-33/include/linux/usb.h
struct urb {/* private: usb core and host controller only fields in the urb */struct kref kref; /* reference count of the URB */int unlinked; /* unlink error code */void *hcpriv; /* private data for host controller */atomic_t use_count; /* concurrent submissions counter */atomic_t reject; /* submissions will fail *//* public: documented fields in the urb that can be used by drivers */struct list_head urb_list; /* list head for use by the urb's* current owner */struct list_head anchor_list; /* the URB may be anchored */struct usb_anchor *anchor;struct usb_device *dev; /* (in) pointer to associated device */struct usb_host_endpoint *ep; /* (internal) pointer to endpoint */unsigned int pipe; /* (in) pipe information */unsigned int stream_id; /* (in) stream ID */int status; /* (return) non-ISO status */unsigned int transfer_flags; /* (in) URB_SHORT_NOT_OK | ...*/void *transfer_buffer; /* (in) associated data buffer */dma_addr_t transfer_dma; /* (in) dma addr for transfer_buffer */struct scatterlist *sg; /* (in) scatter gather buffer list */int num_mapped_sgs; /* (internal) mapped sg entries */int num_sgs; /* (in) number of entries in the sg list */u32 transfer_buffer_length; /* (in) data buffer length */u32 actual_length; /* (return) actual transfer length */unsigned char *setup_packet; /* (in) setup packet (control only) */dma_addr_t setup_dma; /* (in) dma addr for setup_packet */int start_frame; /* (modify) start frame (ISO) */int number_of_packets; /* (in) number of ISO packets */int interval; /* (modify) transfer interval* (INT/ISO) */int error_count; /* (return) number of ISO errors */void *context; /* (in) context for completion */usb_complete_t complete; /* (in) completion routine */struct usb_iso_packet_descriptor iso_frame_desc[0];/* (in) ISO ONLY */
};
在这个结构体内定义了 USB 数据包的传输方向、传输地址、传输大小、传输模式等。这些细节我们不深究,我们只看最后一个成员:
struct usb_iso_packet_descriptor iso_frame_desc[0];
在 URB 结构体的最后,定义一个零长度数组,主要用于 USB 的同步传输。USB 有4种传输模式:中断传输、控制传输、批量传输和同步传输。不同的 USB 设备对传输速度、传输数据安全性的要求不同,所采用的传输模式是不同的。USB 摄像头对视频或图像的传输实时性要求较高,对数据的丢帧不是很在意,丢一帧无所谓 ,接着往下传。所以 USB 摄像头采用的是 USB 同步传输模式。
现在淘宝上的 USB 摄像头,打开它的说明书,一般会支持多种分辨率:从16*16到高清720P多种格式。不同分辨率的视频传输,对于一帧图像数据,对 USB 的传输数据包的大小和个数需求是不一样的。那USB到底该如何设计,去适配这种不同大小的数据传输要求,但又不影响 USB 的其它传输模式呢?答案就在结构体内的这个零长度数组上。
当用户设置不同的分辨率传输视频,USB 就需要使用不同大小和个数的数据包来传输一帧视频数据。通过零长度数组构成的这个变长结构体就可以满足这个要求。可以根据一帧图像数据的大小,灵活地去申请内存空间,满足不同大小的数据传输。但这个零长度数组又不占用结构体的存储空间,当 USB 使用其它模式传输时,不受任何影响,完全可以当这个零长度数组不存在。所以,不得不说,这样的设计真是妙!
04. 指针可以代替零长度数组?
大家在各种场合,可能常常会看到这样的字眼:数组名在作为函数参数进行参数传递时,就相当于是一个指针。在这里,我们千万别被这句话迷惑了:数组名在作为函数参数传递时,确实传递的是一个地址,但数组名绝不是指针,两者不是同一个东西。数组名用来表征一块连续内存存储空间的地址,而指针是一个变量,编译器要给它单独再分配一个内存空间,用来存放它指向的变量的地址。我们看下面这个程序。
程序示例
#include <stdio.h>
#include <string.h>
#include <stdlib.h>struct s1
{int len;int a[0];
};struct s2
{int len;int *a;
};int main(void)
{printf("sizeof(s1): %lu\n", sizeof(struct s1));printf("sizeof(s2): %lu\n", sizeof(struct s2));return 0;
}
执行结果
deng@itcast:~/tmp$ ./a.out
sizeof(s1): 4
sizeof(s2): 16
对于一个指针变量,编译器要为这个指针变量单独分配一个存储空间,然后在这个存储空间上存放另一个变量的地址,我们就说这个指针指向这个变量。而数组名,编译器不会再给其分配一个存储空间的,它仅仅是一个符号,跟函数名一样,用来表示一个地址。
程序示例
#include <stdio.h>
#include <string.h>
#include <stdlib.h>int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9};int b[0];int *p = &a[5];int main(void)
{return 0;
}
在这个程序中,我们分别定义一个普通数组、一个零长度数组和一个指针变量。其中这个指针变量 p 的值为 a[5] 这个数组元素的地址,也就是说指针 p 指向 a[5]。我们接着对这个程序使用 arm 交叉编译器进行编译,并进行反汇编。
deng@itcast:~/tmp$ arm-linux-gcc 7.c -o a.out
deng@itcast:~/tmp$ arm-linux-objdump -D a.out > a.dis
deng@itcast:~/tmp$
从反汇编生成的汇编代码中,我们找到 array1 和指针变量 p 的汇编代码。
00011024 <a>:11024: 00000001 andeq r0, r0, r111028: 00000002 andeq r0, r0, r21102c: 00000003 andeq r0, r0, r311030: 00000004 andeq r0, r0, r411034: 00000005 andeq r0, r0, r511038: 00000006 andeq r0, r0, r61103c: 00000007 andeq r0, r0, r711040: 00000008 andeq r0, r0, r811044: 00000009 andeq r0, r0, r911048: 00000000 andeq r0, r0, r00001104c <p>:1104c: 00011038 andeq r1, r1, r8, lsr r0Disassembly of section .bss:
从汇编代码中,可以看到,对于长度为10的数组 a[10],编译器给它分配了从 0x11024–0x11048 一共40个字节的存储空间,但并没有给数组名 a单独分配存储空间,数组名 a仅仅表示这40个连续存储空间的首地址,即数组元素 a[0] 的地址。而对于 a[0] 这个零长度数组,编译器并没有给它分配存储空间,此时的 a仅仅是一个符号,用来表示内存中的某个地址,我们可以通过查看可执行文件 a.out 的符号表来找到这个地址值。
78: 000082d4 0 FUNC GLOBAL DEFAULT 12 _start79: 000082bc 0 FUNC GLOBAL DEFAULT UND __libc_start_main@@GLIBC_80: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__81: 00000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses82: 00008408 0 FUNC GLOBAL DEFAULT 13 _fini83: 0001104c 4 OBJECT GLOBAL DEFAULT 22 p84: 00008410 4 OBJECT GLOBAL DEFAULT 14 _IO_stdin_used85: 0001101c 0 NOTYPE GLOBAL DEFAULT 22 __data_start86: 00011050 0 NOTYPE GLOBAL DEFAULT ABS __bss_start__87: 0000841c 0 NOTYPE GLOBAL DEFAULT ABS __exidx_end88: 00011024 40 OBJECT GLOBAL DEFAULT 22 a89: 00011020 0 OBJECT GLOBAL HIDDEN 22 __dso_handle90: 00011054 0 NOTYPE GLOBAL DEFAULT ABS __end__91: 0000839c 104 FUNC GLOBAL DEFAULT 12 __libc_csu_init92: 00011054 0 NOTYPE GLOBAL DEFAULT ABS __bss_end__93: 00011050 0 NOTYPE GLOBAL DEFAULT ABS __bss_start94: 00011054 0 NOTYPE GLOBAL DEFAULT ABS _bss_end__95: 00011054 0 OBJECT GLOBAL DEFAULT 23 b96: 00011054 0 NOTYPE GLOBAL DEFAULT ABS _end97: 00011050 0 NOTYPE GLOBAL DEFAULT ABS _edata98: 00008414 0 NOTYPE GLOBAL DEFAULT ABS __exidx_start99: 00008380 28 FUNC GLOBAL DEFAULT 12 main100: 00008290 0 FUNC GLOBAL DEFAULT 10 _init
从符号表里可以看到,b 的地址为 0x11054,在程序 bss 段的后面。b符号表示的默认地址是一片未使用的内存空间,仅此而已,编译器绝不会单独再给其分配一个内存空间来存储数组名。看到这里,也许你就明白了:数组名和指针并不是一回事,数组名虽然在作为函数参数时,可以当一个地址使用,但是两者不能划等号。菜刀有时候可以当武器用,但是你不能说菜刀就是武器。
至于为什么不用指针,很简单。使用指针的话,指针本身也会占用存储空间不说,根据上面的 USB 驱动的案例分析,你会发现,它远远没有零长度数组用得巧妙——不会对结构体定义造成冗余,而且使用起来也很方便。
05. 附录
参考:C语言嵌入式Linux高级编程
【嵌入式】C语言高级编程-长度为0的数组(05)相关推荐
- C语言技巧之长度为0的数组
在gnu c中有一种用法,就是可以使用长度为0的数组,比如说一下这个结构体: struct sample { int length; char store[0]; } 可以像以下这种方式来使用: st ...
- c语言高级程序设计第五版PDF,C语言高级编程.pdf
C语言高级编程 概述 由几个测试程序说开去 预编译与宏 高级预编译介绍 宏的高级用法 变量 变量分类详细解析 我的变量去哪儿了? 大小端对变量的影响 内存与指针 常见内存使用错误大观 指针,又是指针! ...
- 鼠标绘图 c语言,c语言高级编程技术教程 图形显示方式与鼠标输入.doc
c语言高级编程技术教程 图形显示方式与鼠标输入 c语言高级编程技术教程 图形显示方式和鼠标输入 图形显示方式和鼠标输入 问题的提出编写程序,使用鼠标进行如下操作:按住鼠标器的任意键并移动,十字光 标将 ...
- 高级编程中C语言属于,c语言高级编程
c语言高级编程 C高级编程 责任编辑:admin 更新日期:2005-8-6 深入了解C语言(函数的参数传递和函数使用参数的方法) tangl_99(原作) 关键字 C语言,汇编,代码生成,编译器 C ...
- Go 学习推荐 —(Go by example 中文版、Go 构建 Web 应用、Go 学习笔记、Golang常见错误、Go 语言四十二章经、Go 语言高级编程)
Go by example 中文版 Go 构建 Web 应用 Go 学习笔记:无痕 Go 标准库中文文档 Golang开发新手常犯的50个错误 50 Shades of Go: Traps, Gotc ...
- 《go语言圣经》+《Mastering.GO-cn》+《go语言高级编程》PDF下载
公众号[爱吃橙子的搬砖小徐]开通啦,后续将会同步更新,欢迎订阅 回复[java面试]获得两套面试宝典 回复[golang]获得go语言学习三部曲 <go语言圣经>+<Masterin ...
- matlab高级教程教材,MATLAB语言高级编程 PDF_IT教程网
资源名称:MATLAB语言高级编程 PDF 本书共分8章,主要介绍了matlab的概述.matlab安装与工作桌面:matlab的编程基础,包括matlab的变量.matlab的运算符.矩阵的创建及运 ...
- C语言长度为0的数组
前面在看Xen的源码时,遇到了一段代码,如下所示: 注意上面最后一行的代码,这里定义了一个长度为的数组,这种用法可以吗?为什么可以使用长度为0 的数组?长度为的数组到底怎么使用?--这篇文章主要针对该 ...
- C语言中长度为0的数组
前面在看Xen的源码时,遇到了一段代码,如下所示: 注意上面最后一行的代码,这里定义了一个长度为的数组,这种用法可以吗?为什么可以使用长度为0 的数组?长度为的数组到底怎么使用?--这篇文章主要针对该 ...
最新文章
- iqoo升级鸿蒙,vivo iQOO 3升级更新PD1955_A_1.12.27固件rom全量刷机包
- Storm 01之 Storm基本概念及第一个demo
- jq js json 转字符串_JQuery如何把JSON字符串转为JSON对象
- win10电脑如何安装Openssh?开启Server服务?
- 关于Vmware下NAT模式物理机无法ping通虚拟机但是可以上网的解决方法
- Jupyter Notebook简介及默认路径修改
- 2+22+222+2222(直到n个2的精确计算)
- [蓝桥杯][2013年第四届真题]剪格子-dfs
- ElasticJob-分布式作业调度神器,你们还在用Quartz吗
- Windbg SOS and CLR版本不一致的解决方案
- MSSQL 2005数据库安全设置
- Redis面试 - redis 的雪崩和穿透?
- 2021-08-02 修改表
- 学习ExtJs教程初级
- 假如时光能够倒流, 我会这么学习Java
- <机器学习 房价预测 >对贝壳租房网 信息爬取 及处理。
- Lunix入门到精通-网络排查工具 MTR
- HIVE 实现均匀抽样
- 记一次天池比赛 - 性能挑战赛道
- 阿里云 短信服务——短信发送频率限制
热门文章
- jQuery-对Select的操作集合
- Fedora9中的gcc
- 如何在dw上编写php_用dw制作php网站视频教程
- c语言程序链接过程,C语言简明教程(二):C程序编译链接过程和实例对照详解...
- 变频器端子阻抗3k_PLC与变频器连接问题分析
- SpringBoot+SpringSecurity之多模块用户认证授权同步
- tiny4412的烧录工具minitool安装【学习笔记】
- django ORM 操作
- [转]解读Unity中的CG编写Shader系列3——表面剔除与剪裁模式
- IE6 PNG 透明的方法