王剑编程网

分享专业编程知识与实战技巧

在C语言程序中定义结构体时,交换下成员顺序,编译器居然会报错

在我之前的文章里,曾讨论过可以在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等嵌入式开发,喜欢我的文章就关注一波吧,可以看到最新更新和之前的文章哦。

未经许可,禁止转载。

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言