关于柔性数组…
什么是柔性数组?
先来了解一下“不完整类型
(incomplete type)”,不完整类型是这样一种类型,它缺乏足够的信息(如长度)去描述一个完整的对象。C99标准支持不完整类型,其形式形如int a[],但也有一些编译器把int a[0] 作为非标准扩展来支持。
知道了不完整类型,就可以去了解柔性数组了。在日常的编程中,有时候需要在结构体中存放一个长度动态的字符串,一般的做法,是在结构体中定义一个指针成员,这个指针成员指向该字符串所在的动态内存空间,例如:
1 | typedef struct test |
这样子是很容易想到的做法,但是却有一点不方便的地方。比如为test对象分配空间之后,还需要再为p指针分配一次空间。这样子,如果第二次malloc失败了,就必须要回滚释放第一个分配的结构体,这样带来了编码麻烦。
于是,有一种做法是,只进行一次分配。可以把代码修改为:
1 | typedef struct test |
这样子进行分配的时候,只需要:
1 | char a[] = "hello world"; |
释放结构体的时候,也不会出现忘记释放指针导致的内存泄露问题,因为只需要:free(stpTest);
在进行Linux内核开发或者嵌入式开发时,经常会遇到结构体的最后出现char data[], char data[0], char data[1],这样的代码,这就是柔性数组的实现。柔性数组也并没有定义柔性数组,只是所支持的不完整类型产生了柔性数组这样神奇的结构。
使用柔性数组的好处
其实上面也说到了,总结而来就是:
- 不需要初始化,数组名直接就是所在的偏移。
- 不占任何空间,指针需要占用int长度空间,空数组不占任何空间。(注意,char data[1];这种形式是占用一个单位的空间的)
- 空间一次分配,防止内存泄漏。
- 分配连续的内存,减少内存碎片化。(因为指针所分配的空间不是连续的,而数组占用连续的空间)
使用柔性数组时需要注意的
- 必须是结构体的最后一个成员
- 柔性数组之上,需要有其他的成员(结构体中不能只有一个柔性数组)
sizeof
返回的结构体的大小不包括柔性数组的内存(如果是char data[1]就会有一个单位的空间)
使用char data[0] 和 char data[1]的区别
结构体中最后一个成员为[0]长度数组的用法:这是个广泛使用的常见技巧,常用来构成缓冲区。比起指针,用空数组有这样的优势:(1)、不需要初始化,数组名直接就是所在的偏移;(2)、不占任何空间,指针需要占用int长度空间,空数组不占任何空间。“这个数组不占用任何内存”,意味着这样的结构节省空间;“该数组的内存地址就和它后面的元素地址相同”,意味着无需初始化,数组名就是后面元素的地址,直接就能当指针使用。
这样的写法最适合制作动态buffer,因为可以这样分配空间malloc(sizeof(structXXX) + buff_len); 直接就把buffer的结构体和缓冲区一块分配了。用起来也非常方便,因为现在空数组其实变成了buff_len长度的数组了。这样的好处是:(1)、一次分配解决问题,省了不少麻烦。为了防止内存泄露,如果是分两次分配(结构体和缓冲区),那么要是第二次malloc失败了,必须回滚释放第一个分配的结构体。这样带来了编码麻烦。其次,分配了第二个缓冲区以后,如果结构里面用的是指针,还要为这个指针赋值。同样,在free这个buffer的时候,用指针也要两次free。如果用空数组,所有问题一次解决。(2)、小内存的管理是非常困难的,如果用指针,这个buffer的struct部分就是小内存了,在系统内存在多了势必严重影响内存管理的性能。要是用空数组把struct和实际数据缓冲区一次分配大块问题,就没有这个问题。如此看来,用空数组既简化编码,又解决了小内存碎片问题提高了性能。
结构体中最后一个成员为[1]长度数组的用法:与长度为[0]数组的用法相同,改写为[1]是出于可移植性的考虑。有些编译器不支持[0]数组,可将其改成[]或[1].
结构体最后使用0或1长度数组的原因:主要是为了方便的管理内存缓冲区(其实就是分配一段连续的内存,减少内存的碎片化),如果直接使用指针而不使用数组,那么,在分配内存缓冲区时,就必须分配结构体一次,然后再分配结构体内的指针一次,(而此时分配的内存已经与结构体的内存不连续了,所有要分别管理即申请和释放)而如果使用数组,那么只需要一次就可以全部分配出来,反过来,释放时也是一样,使用数组,一次释放。使用指针,得先释放结构体内的指针,再释放结构体,还不能颠倒顺序。
示例代码
1 |
|