C语言陷阱与技巧第37节,长度为0的数组有什么意义?空数组的实际使用意义

数组是C语言中非常重要的概念,特别适合存储和处理批量相同类型的数据。C语言可以快速的随机访问数组元素,这使得数据排序查找等操作简单易行。

长度为 0 的“空数组”

在C语言程序开发中,定义一个数组是方便的,例如:

int array[128];

上面这行C语言代码定义了一个 int 型的数组 array,array 可以安全的存放 128 个 int 型的数据。当然,C语言也允许不显式指定数组元素的个数,例如:

int array[] = {1, 2, 3};

不过这种定义数组的方式必须有初始化信息,因为C语言编译器在编译阶段需要确定数组的元素个数(编译器将根据初始化值的个数,确定数组元素个数)。所以下面这种定义数组的方法是非法的:

int array_2[];      // 非法

上面两点相信读者已经非常清楚,不过很少有人注意,在 gcc v4.8.4 下,下面这种定义数组的方式也是允许的,请看:

int array[128] = {};

这行C语言代码定义了一个可以安全存放 128 个元素的数组 array 并赋值,赋值内容为空 {} 。按照C语言语法,在定义数组时初始化,未指定的元素值将被赋值为 0,所以执行完这行C语言代码后,array 的所有元素都被设置为 0。

上面讨论到,只要在定义数组时有初始化信息,可以不指定数组元素个数,所以下面这种写法也是允许的,请看:

int array_3[] = {};

可能会产生的疑惑是,赋值内容为空 {},编译器计算 array_3 长度时,会得到什么结果呢?得到答案最简单直接的方法就是实验,使用 printf() 语句将其长度打印出来:

printf("len of array_3: %d\n", sizeof(array_3)/sizeof(int));

执行这行C语言代码,得到如下输出:

len of array_3: 0

发现数组 array_3 的长度为 0,事实上,我们也可以直接定义长度为 0 的数组,相关C语言代码如下,请看:

int array_3[0];
// 等价于
int array_3[] = {};

长度为 0 的数组似乎没有什么意义,那为何C语言编译器允许其存在呢?

首先应该明白,长度为 0 的数组在 C语言中并非没有实际意义,读者如果读过我之前的文章,应该能够发现C语言作为一门非常灵活的编程语言,是可以操作一些“意想不到”的内存的。例如,定义结构体 s:

struct s{
    char    c;
    float   f;
};

如果希望在堆中申请一块内存给结构体使用,可以调用 malloc() 函数,一般来说,申请内存的长度等于结构体长度,相关C语言代码如下:

struct s *t = (struct s *)malloc(sizeof(struct s));

不过,C语言并不对申请的内存长度做严格限制,因此 t 也可以管理大于 sizeof(struct s) 长度的内存,例如:

struct s *t = (struct s *)malloc(sizeof(struct s) + 10);

这样一来,t 不仅可以正常按照结构体 s 的数据结构使用内存,还可以不冲突的使用其之后的 10 个字节。具体来说,成员 f 之后的 10 个字节不属于结构体 s,因此C语言程序可以独立地使用这块内存,下面是一个例子:

char *buf = (char *)(&(t->f)) + sizeof(t->f);
t->c = 5;
t->f = 3.14;
strcpy(buf, "hello");

printf("t->c: %d, t->f: %0.2f, buf: %s\n", t->c, t->f, buf);


编译这段C语言代码并执行,得到如下输出:

# gcc t.c
# ./a.out 
t->c: 5, t->f: 3.14, buf: hello

可见程序并无内存冲突,这种特性允许C语言定义长度不固定的变长结构体。只不过,使用 buf 的时候需要程序员手动指定地址,比较麻烦,而且如果对结构体 s 的成员做调整,可能还需要在整个C语言工程里修改 buf 的地址。

这种情况下,长度为 0 的数组就很有用了,现在为结构体 s 新增成员,相关C语言代码如下,请看:

struct s{
    char    c;
    float   f;
    char    arr[0];
};

成员 arr 紧跟在成员 f 之后,它的长度为 0,因此不额外占用内存。不过,通过 arr 可以很方便的访问多出来的内存

struct s *t = (struct s *)malloc(sizeof(struct s) + 10);
if(!t)
    exit(1);
t->c = 5;
t->f = 3.14;
strcpy(s->arr, "hello");

就算需要调整结构体 s 的成员,只要确保 arr 是最后一个成员,它就能安全的访问多出来的内存,C语言工程代码无需再做改动。

小结

本节简要讨论的 0 长度的“空数组”,并在此基础上讨论了C语言中的变长结构体,可以看出即使是长度为 0 的数组也是有实际应用意义的。事实上,C语言是一门非常严谨的编程语言,它的语法简单,但是却能实现各种复杂的特性,这也从侧面说明C语言是一门极其重视基本功的编程语言。

阅读更多:   杂谈 , C语言
添加新评论

icon_redface.gificon_idea.gificon_cool.gif2016kuk.gificon_mrgreen.gif2016shuai.gif2016tp.gif2016db.gif2016ch.gificon_razz.gif2016zj.gificon_sad.gificon_cry.gif2016zhh.gificon_question.gif2016jk.gif2016bs.gificon_lol.gif2016qiao.gificon_surprised.gif2016fendou.gif2016ll.gif