关于柔性数组…

什么是柔性数组?

先来了解一下“不完整类型(incomplete type)”,不完整类型是这样一种类型,它缺乏足够的信息(如长度)去描述一个完整的对象。C99标准支持不完整类型,其形式形如int a[],但也有一些编译器把int a[0] 作为非标准扩展来支持。

知道了不完整类型,就可以去了解柔性数组了。在日常的编程中,有时候需要在结构体中存放一个长度动态的字符串,一般的做法,是在结构体中定义一个指针成员,这个指针成员指向该字符串所在的动态内存空间,例如:

1
2
3
4
5
6
typedef struct test  
{
int a;
double b;
char *p;
};

这样子是很容易想到的做法,但是却有一点不方便的地方。比如为test对象分配空间之后,还需要再为p指针分配一次空间。这样子,如果第二次malloc失败了,就必须要回滚释放第一个分配的结构体,这样带来了编码麻烦。

于是,有一种做法是,只进行一次分配。可以把代码修改为:

1
2
3
4
5
6
typedef struct test  
{
int a;
double b;
char p[0]; // 若有些编译器不支持,可以改成char p[];
};

这样子进行分配的时候,只需要:

1
2
3
char a[] = "hello world";
test *stpTest = (test *)malloc(sizeof(test) + strlen( a ) + 1 );
strcpy(stpTest + 1, a );

释放结构体的时候,也不会出现忘记释放指针导致的内存泄露问题,因为只需要:free(stpTest);

在进行Linux内核开发或者嵌入式开发时,经常会遇到结构体的最后出现char data[], char data[0], char data[1],这样的代码,这就是柔性数组的实现。柔性数组也并没有定义柔性数组,只是所支持的不完整类型产生了柔性数组这样神奇的结构。

使用柔性数组的好处

其实上面也说到了,总结而来就是:

  1. 不需要初始化,数组名直接就是所在的偏移。
  2. 不占任何空间,指针需要占用int长度空间,空数组不占任何空间。(注意,char data[1];这种形式是占用一个单位的空间的)
  3. 空间一次分配,防止内存泄漏。
  4. 分配连续的内存,减少内存碎片化。(因为指针所分配的空间不是连续的,而数组占用连续的空间)

使用柔性数组时需要注意的

  1. 必须是结构体的最后一个成员
  2. 柔性数组之上,需要有其他的成员(结构体中不能只有一个柔性数组)
  3. 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include<iostream>
using namespace std;

typedef struct _SoftArray {
char ch;
int arr[0];
}SoftArray;

int main() {
cout << sizeof(SoftArray) << endl;

const int LENGTH = 10;
SoftArray* sa = (SoftArray*)malloc(sizeof(SoftArray) + sizeof(int) * LENGTH);

for (int i = 0; i < LENGTH; i++) {
sa->arr[i] = i;
}

for (int i = 0; i < LENGTH; i++) {
cout << sa->arr[i] << " ";
}
cout<<endl;
free(sa);
return 0;
}

// result:
// 4
// 0 1 2 3 4 5 6 7 8 9

参考

  1. http://blog.chinaunix.net/uid-26750459-id-3191136.html
  2. http://blog.csdn.net/ce123_zhouwei/article/details/8973073
  3. https://blog.csdn.net/fengbingchun/article/details/24185217