正文
大家好,我是bug菌!
之前bug菌有发布一篇不建议大家把什么东西都往结构体塞<不要什么变量都想往"结构体"里塞~>的文章,主要的观点其实是让大家能够理性的组织自己的数据结构,不要面向结构体编程,很多朋友都在后台留言发表了自己的一些看法,而主要是关于其结构体成员嵌套会不会影响程序执行效率等,观点主要是两个方面:
1)不同平台对结构体和全局变量的访问不同;
2)编译器优化能够对这些数据访问轻轻松松的优化;
其实把结构体嵌套多层这样的话题跟"干掉if_else"等等都是类似的,对于大部分开发人员和普通项目基本上是不怎么关心的,因为所选择的MCU等等性能上都能够完全覆盖。
而只有当你正在为了一些函数、中断等等的执行时间太长而发愁的时候,这些东西或许才会吸引你,此时此刻你的思考方向展现着你真正的技术水平。
其实bug菌经常会在文章中表达自己的一些思维观点,比如“时间换空间、空间换时间”;“具体情况具体分析”;"一切没有前提条件的结论都是耍流氓"等等,所以没有永恒的真理,只有相对的可行,关键还是要有一个思考和研究的过程。
1
不同平台问题
不同架构的CPU,都会有自己的一些特色,从编程语言这个层面大部分工程师基本上只会研究到指令,对应着就是不同的指令集,而程序里对变量的访问基本上就是对内存的访问,而访问的方式不同又引出来一堆寻址方式,常见的就是立即数寻址、直接寻址、间接寻址等等,寻址速度上来说一般 情况: 立即数寻址>直接寻址>间接寻址。
编译器会把C程序转化为最终的汇编指令,而C程序与汇编指令并不是一一对应的关系,很好理解 : 汇编语言也一门编程语言,在编程语言的层面他们是级别;实现同样一个功能有非常多的编码处理方式,而不同的处理方式又会带来不同的效果,所以C编译器都会有一些优化选项给用户来选择,从而满足大家的需求,这也是目前编译器开发人员努力的方向。
而对于部分C代码编译最终还是达不到执行的要求,就只能老老实实自己来编写汇编程序了。
而对于结构体的成员访问如果能够直接确定其偏移地址,编译器一般会以直接寻址的方式访问;而对于运行时才能确定其地址,则一般通过结构体的基地址间接找到结构体成员,即“基地址+偏移”的方式,所以说不管结构体嵌套多少层,不会随着嵌套层数的增加而成比例的增加访问时间。
如下是在devC++,进行O0-编译结果:
参考代码:
1#include <stdio.h>
2#include <stdlib.h>
3
4typedef struct _tag_Obj{
5 int var;
6
7 struct _tag_member1{
8 int var;
9
10 struct _tag_member2{
11 int var;
12
13 struct _tag_member3{
14 int var;
15
16 struct _tag_member4{
17 int var;
18
19 }member4;
20
21 }member3;
22
23 }member2;
24
25 }member1;
26}stObj;
27
28stObj obj1;
29
30
31int main(int argc, char *argv[]) {
32 int index = 0;
33 int a = 0;
34
35 a = obj1.member1.var;
36 a = obj1.member1.member2.member3.member4.var;
37
38 return 0;
39}
汇编:
从汇编可以看出,这种形式的结构体,不管结构体成员嵌套多少层,其访问的结构体成员都采用直接寻址的方式。
参考代码:
1#include <stdio.h>
2#include <stdlib.h>
3
4typedef struct _tag_Obj{
5 int var;
6
7 struct _tag_member1{
8 int var;
9
10 struct _tag_member2{
11 int var;
12
13 struct _tag_member3{
14 int var;
15
16 struct _tag_member4{
17 int var;
18
19 }member4;
20
21 }member3;
22
23 }member2;
24
25 }member1;
26}stObj;
27
28#define MAX_NUM 5
29stObj obj[MAX_NUM];
30
31
32
33int main(int argc, char *argv[]) {
34 int index = 0;
35 int a = 0;
36
37 a = obj[index].member1.var;
38 a = obj[index].member1.member2.member3.member4.var;
39
40 return 0;
41}
对于这种嵌套有数组的结构体,且访问数组内成员,需要在运行时根据index变量来最终确定成员的地址,同样我们来看看汇编:
这里首先获得index变量,然后计算出结构体成员的偏移进行访问,这里采用了一种间接寻址的方式,相比前面的小例子增加了很多指令,运行也必然要更耗时,如果每一层嵌套均需要索引,那么耗时可想而知,处理办法我在前面的文章有提及,这里就不在多说了~
2
试着调高优化等级
经常有朋友问我要不要开启优化,其实不开启优化的话,大部分C代码都是按照编写者逻辑几乎一对一的"翻译",所以打开C代码和汇编代码也可以按照逻辑对应上,当然一些实在是太无脑的C代码,即使不开优化等级,编译器也是会处理的。
所以在没有开启优化的情况下,编译器把更多的逻辑和优化处理交给C程序员,而一旦你开启了优化等级,那么编译器会自主的根据一些优化规则来对编译过程进行调整,此时进行C与汇编的对照就会有所差异。
下面我们把Dev的编译等级提升为High,然后对程序进行相应的修改,不然前面的程序直接被优化掉,灰都不剩~。
参考代码:
1int main(int argc, char *argv[]) {
2 int index = 0;
3 int a = 0;
4
5 scanf(" %d",&index);
6 a = obj[index].member1.member2.member3.member4.var;
7 printf(" a = %d\n",a);
8 return 0;
9}
通过获得index的值,然后索引相应的结构体成员,相应的汇编如下:
从汇编可以看出其也是分为两步,获得index,然后通过间接寻址获得最终的结构体成员,不过相比之前没有优化要短小精悍很多。
所以对于做算法的伙计们,优化代码和算法的执行效率, 优化等级是需要好好研究的,而对于所在平台优化情况不是特别熟悉的朋友,还是先研究好会优化什么内容,然后再开启优化选项等级和选项,以免造成运行异常。
好了,具体情况再具体分析吧~
最后
今天的内容就到这里了,觉得有所收获,记得点个赞哦~~
推荐专辑 点击蓝色字体即可跳转
☞ MCU进阶专辑
☞ “bug说”专辑
☞ 专辑|手撕C语言
☞ 专辑|经验分享