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

yumo6661周前 (07-03)技术文章8

在我之前的文章里,曾讨论过可以在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语言编译器就报错了呢?

读者应该注意"如果不考虑内存填充等其他因素"这句话,其实所谓的"其他因素"就是导致小明问题出现的原因。

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

未经许可,禁止转载。

相关文章

“告别 8 万行 C++ 代码,我用 4 千行 C 代码就搞定了!”

摘要:这一次,Zig 要彻底告别 C++ 了。链接:https://ziglang.org/news/goodbye-cpp/声明:本文为 CSDN 翻译,未经允许禁止转载。作者 | Andrew K...

C语言编译过程

1、GCC、glibc和GNU C的关系1.1.1 GCCGCC全称GNU Compiler Collection,是GNU项目的一部分,主要是一套编译器工具集,支持多种编程语言,包括C、C++、Ob...

C语言入门指南

当然!以下是关于C语言入门编程的基础介绍和入门建议,希望能帮你顺利起步:C语言入门指南一、什么是C语言?C语言是一门通用的高级程序设计语言,由Dennis Ritchie在20世纪70年代开发,被广泛...

使用C语言写一个"Hello, World!" 程序

默认您已搭建好语言的开发运行环境编写代码使用文本编辑器(如Notepad++)创建一个新的源文件,扩展名为.c,并输入如下内容到文件中,将文件保存为 hello.c:#include <stdi...

编译器动手实践之:实现C语言函数定义的语法解析

C语言是一种面向过程的语言,面向过程是指,将实现一个逻辑功能的代码集中到一起,每次需要使用的时候,再调用这些代码集合,这种代码集合就是函数。写C语言其实就是写一个个函数,因此对函数实现的语法解析是C语...