在我之前的文章里,曾讨论过可以在C语言结构体里定义不指定长度的数组,以便后期根据需要扩展。小明看了这样的文章后,立刻就在自己的代码中使用了,但是他遇到了一个问题。

遇到了一个问题
C语言定义结构体成员,有顺序要求吗?
为了便于讨论,本文将相关C语言代码做了精简。小明第一编写了下面这样的代码:
struct s {
int a;
};
struct z {
int a;
struct s b[];
};
int main(void) {
return 0;
}
一切正常,结构体 z 中的 b 成员可根据后期需求 扩展内存。不过,小明稍后写出了下面这样的C语言代码:
struct z {
struct s b[];
int a;
};
与上面的代码相比,仅仅是交换了结构体z的两个成员顺序而已。不过此时再编译,会发现编译器报错:”field has incomplete type 'struct s []'”这是怎么回事呢?难道说C语言中的结构体在定义成员时,还有顺序要求吗?

有顺序要求吗?
讨论
第一应该清楚,在C语言程序开发中定义结构体时,如果不思考内存填充等其他因素,定义成员的顺序实则并不重大,例如
struct s{
int a;
int b;
};
和
struct s{
int b;
int a;
};
这两种定义方式并未带来本质上的差异,那为什么小明交换定义结构体成员的顺序,C语言编译器就报错了呢?

为什么C语言编译器报错呢?
读者应该注意”如果不思考内存填充等其他因素”这句话,实则所谓的”其他因素”就是导致小明问题出现的缘由。
struct z {
struct s b[];
int a;
};
请看这段C语言代码,如果像上面这样定义结构体 z,编译器显然无法确定成员 b 究竟会占用多少内存。这意味着如果后面还有其他成员,编译器就无法确定这些成员的偏移量。
在之前旧版本C语言中,struct s b[]; 是不允许作为结构体的成员,这样使得内存管理变得烦人。

专栏
C语言经典面试题目详解
作者:嵌入式时代
26.6币
135人已购
查看
举个简单的例子,假设在某段C语言程序中,我们需要定义结构体记录公司员工的信息,包括 name 成员用于记录员工姓名,员工姓名长度可长可短,思考到要存储很长的员工姓名,可以将 name 定义成足够大的数组。

“足够大”是暧昧的说法
“足够大”是暧昧的说法,意味着许多内存空间在许多时间是白白浪费的,这对于资源匮乏的嵌入式程序开发来说是超级不可取的。所以,似乎使用动态内存分配是更好的选择,请看下面这段C语言代码:
length = strlen(my_string); foo = malloc(sizeof(MYSTRUCTURE) + length + 1); foo->name = (void *)foo + sizeof(MYSTRUCTURE); memcpy(foo->name, my_string, length + 1);
应该清楚,这样做能够最大程度的节约内存空间,但是降低了C语言代码的可读性,也比较容易出错。
为了解决这个问题,一些编译器添加了非标准扩展以允许在结构体的末尾使用”未知大小的数组”。这使得C语言程序开发变得更容易,效率也更高,同时也更安全,由于不需要额外的指针成员了。

这样的扩展真的很好用
这样的扩展真的很好用,所以最终被C语言标准采用了(也许在C99中,我记不清了)。
小结
“允许在结构体的末尾使用”未知大小的数组””,这里读者应注意”末尾”一词。将”未知大小的数组”作为C语言结构体的成员是有缘由的,由于后面没有其他成员,编译器也无需再烦心确定后续成员的偏移量了。
因此,小明的问题重点倒不在于结构体语法了,而是”确定性”,C语言编译器在编译代码时,若是遇到由于”未知大小的数组”而不能确定结构体后续成员偏移的情况,必然是不能处理的,只好报错了。

专栏
手把手教你学Linux C语言编程
作者:嵌入式时代
49.9币
132人已购
查看

点个关注吧
欢迎在评论区一起讨论,质疑。文章都是手打原创,每天最浅显的介绍C语言、linux等嵌入式开发,喜爱我的文章就关注一波吧,可以看到最新更新和之前的文章哦。
未经许可,禁止转载。


