零长度数组
最近在准备分析Linux input子系统,发现内核代码里面有很多小技巧。特此记录下,如有不足之处,敬请指正。
一、前言
在日常的编程中,有时候需要在结构体中存放一个长度动态的字符串,比如说,我们要在结构体中存放一个名字,但是这个名字的长度是未知的。于是,我们就会采用以下两种方法来解决这个问题。
注:以下的代码都是在下面的平台进行验证测试的:
Linux version 2.6.32-38-generic (buildd@allspice) (gcc version 4.4.3 (Ubuntu 4.4.3-4ubuntu5) ) #83-Ubuntu SMP Wed Jan 4 11:12:07 UTC 2012
二、为数据结构成员申请空间
碰到上面的情况,一般而言我们会这么做:首先在结构体中定义一个指针成员,然后为这个指针成员malloc()
一定长度的空间,进而往该空间写入字符串。
#include <stdio.h>
#include <malloc.h>
#include <string.h>
struct node{
int num;
int data;
char *name; // 结构体成员定义为指针
};
char test_name[] = "testname";
int main(void)
{
struct node *test_node;
int lenght = strlen(test_name);
test_node = (struct node*)malloc(sizeof(struct node));
test_node->name = (char *)malloc(lenght);
strncpy(test_node->name, test_name, lenght);
printf("sizeof(struct node) = %d\n", sizeof(struct node));
printf("num addr:%p\n", &(test_node->num));
printf("data addr:%p\n", &(test_node->data));
printf("name addr:%p\n", test_node->name);
printf("name = %s\n", test_node->name);
printf("name[0] = %c\n", test_node->name[0]);
printf("name[%d] = %c\n", lenght-1, test_node->name[lenght-1]);
free(test_node->name);
free(test_node);
}
上述代码的执行结果为:
sizeof(struct node) = 16
num addr:0x10ba010
data addr:0x10ba014
name addr:0x10ba030
name = testname
name[0] = t
name[7] = e
从中,我们可以看出:
1、如果想要实例化test_node
,第一次要为test_node
申请大小为sizeof(struct node)
的空间,第二次再为其成员name
申请lenght
大小的空间。
首先我们要调用两次malloc()
,调用两次,意味着最后也要跟着free()
两次;其次,如果第二次为name
成员申请空间失败的话,需要返回回去将test_node
free掉。这样工作量就上来了,而且容易导致malloc()
与free()
没对应进而出现内存错误。
2、我们看到,sizeof(struct node)
的大小为16,即4+4+8的大小。这个可以和下面的代码进行对比。
3、通过打印test_node
三个成员的地址分别为:
num addr:0x10ba010
data addr:0x10ba014
name addr:0x10ba030
很明显,成员name
的地址与成员num
和data
的地址并不是连续的。小内存的管理是非常困难的,如果用指针,这个name
的部分就是小内存了,在系统内存在多了势必严重影响内存管理的性能。要是用空数组把结构体和实际数据缓冲区一次分配大块问题,就没有这个问题。
三、采用空数组
为了规避上述代码的问题,我们采用下面的方法来解决:
#include <stdio.h>
#include <malloc.h>
#include <string.h>
struct node{
int num;
int data;
char name[0]; // 结构体最后的成员定义一个长度为[0]的数组
};
char test_name[] = "testname";
int main(void)
{
struct node *test_node;
int lenght = strlen(test_name);
test_node = (struct node*)malloc(sizeof(struct node)+lenght);
//strncpy((char *)test_node+1, test_name, lenght);
strncpy(test_node->name, test_name, lenght);
printf("sizeof(struct node) = %d\n", sizeof(struct node));
printf("num addr:%p\n", &(test_node->num));
printf("data addr:%p\n", &(test_node->data));
printf("name addr:%p\n", test_node->name);
printf("test_node+1 addr:%p\n", test_node+1);
printf("name = %s\n", test_node->name);
printf("name[0] = %c\n", test_node->name[0]);
printf("name[%d] = %c\n", lenght-1, test_node->name[lenght-1]);
free(test_node);
}
运行结果为:
sizeof(struct node) = 8
num addr:0x1574010
data addr:0x1574014
name addr:0x1574018
test_node+1 addr:0x1574018
name = testname
name[0] = t
name[7] = e
通过比较,我们不难看出:
1、此时,实例化test_node
只调用一次malloc()
,意味着最后也只要free()
一次。为其申请了一个长度为sizeof(struct node)+lenght
的空间,所申请到的空间是连续的,没有小内存的情况出现。减小了对内存管理性能的影响。
2、同样的,sizeof(struct node)
为8,即4+4,因此我们可以知道这里的成员name
并没有占用空间,即使占用一个指针大小的空间都没有,因此这种方式可以节省空间。
3、此时test_node
三个成员的地址分别为:
num addr:0x1574010
data addr:0x1574014
name addr:0x1574018
可以明显的看到,这个地址是连续的。因为我们只malloc()
一次。
4、并且,我们还知道test_node+1
的地址和test_node->name
的地址是一样的,说明两者指向同一块内存区域,这更进一步的验证了成员name
并没有占用空间。
四、柔性数组成员
C99使用不完整类型实现柔性数组成员,在C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组(flexible array)成员(也叫伸缩性数组成员),但结构中的柔性数组成员前面必须至少一个其他成员。柔性数组成员允许结构中包含一个大小可变的数组。柔性数组成员只作为一个符号地址存在,而且必须是结构体的最后一个成员,sizeof 返回的这种结构大小不包括柔性数组的内存。柔性数组成员不仅可以用于字符数组,还可以是元素为其它类型的数组。包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
参考文献:
深入浅出C语言中的柔性数组
第八章 柔性数组成员
结构体中最后一个成员为[0]或[1]长度数组(柔性数组成员)的用法
更多推荐
所有评论(0)