在我之前的文章里,曾讨论过可以在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语言程序中,我们需要定义结构体记录公司员工的信息,包括 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语言编译器在编译代码时,若是遇到因为"未知大小的数组"而不能确定结构体后续成员偏移的情况,必然是不能处理的,只好报错了。
点个关注吧
欢迎在评论区一起讨论,质疑。文章都是手打原创,每天最浅显的介绍C语言、linux等嵌入式开发,喜欢我的文章就关注一波吧,可以看到最新更新和之前的文章哦。
未经许可,禁止转载。