标签:
过去你的自己,你好。
照顾宝宝,写完第一篇就没什么时间,既然上次讲的就是数据结构,这次也讲点相关的。
其实接触柔性数组也是个比较奇妙的过程,你以后会遇到个学长,毕业后从事软件行业,在中兴锻炼过,将会给你很多软件方面的启发。一次讨论结构体内数据结构
类型强制转换的问题时(剧透一下,是你用的编译器中的一个bug导致了ARM操作非对齐的地址所以进入hardfault,这个问题和今天讲的没关系,以
后再开题),听到了“柔性数组”这个关键字,当时没听明白也就没有去深究。然后某日在论坛看到篇好帖讲C语言的一些高级用法,看到了一个疑似“柔性数组”
的说明,细究之后恍然大悟原来柔性数组是这么回事。不过另一个问题来了,感觉没有什么场合会用到,也就没有深入下去。结果就在最近,在做模块化编程时,发
现了用柔性数组极佳的环境。我就是这样开始正视柔性数组。开篇讲了这么多废话的目的是要告诉你:1、多向前辈学习;2、注意前辈不经意吐露的“关键
字”;3、有机会一定要深究“关键字”,会有很大收获;4、对新的知识再深究下应用环境。
数组一般都是静态的,所谓静态就是在编译之时就已经确定的。如果是全局数组,则数组的大小及存放在内存中的位置就确定了(静态储存区);若是局部数组且编
译器不支持VLA(可变长度数组),那该数组在内存的位置就在运行时分配(栈区域),但是数组的大小还是在编译之时就会确定下来(若编译器支持C99的
VLA,则仅限局部数组可使用可变长度)。从上面两种情况可以看出,不管数组最终存放在哪里,数组的大小是需要在编译时确定的(C语言中规定申请数组时中
括号内的数据必须是常量),那问题就来了,如果你想开发一个软件模块且日后不想根据不同的应用修改代码,那用我们上述方法的数组肯定是不行的,因为不知道
模块在被使用时数组的大小(其实可以简单的方法解决--宏定义数组大小,有机会再解释)。
上述就是问题的提出,下面介绍几个豆知识:
1、int a = 0;那a就是变量名,其实就是内存的一个别名。若a的地址为0x200000FE,则a就是内存0x200000FE的别名
2、struct t
{
unsigned char wLen;
unsigned char awData[7];
};
结构体t的大小为8个字节,如果t申请在内存为0x20000000的内存上,那t.awData[6]的地址位0x20000007上,t.awData[7]的地址为0x20000008,即结构体t所有成员所占内存的后面一个字节内存。
再回到静态/动态数组上来,目测静态数组不能满足模块化编程的需要,那就看看有没有动态数组。既然是动态,那就是在设备运行时进行操作,而数组又是一种数 据结构,也需要分配内存,这样很容易就想到“动态分配malloc”来解决数组大小既定的问题。看一下下面的结构体。
1 typedef struct _tTest_ 2 { 3 unsigned char wLen; /* Length of data */ 4 unsigned char awData[0]; /* Data field */ 5 }T_TEST; 6 7 T_TEST t = {0}; /* Create a "t" */
依旧是类似的结构体t,不同的是成员数组awData的大小为0,通过sizeof (T_TEST)可以发现结构体t的大小为1 byte,说明数组awData不占内存,那这个数组还有什么意义呢?看一下豆知识1,变量名实际上就是内存地址的别名,那么这个长度为0的数组实际上就 是某个内存地址的一个别名。那t.awData[0]是哪个内存地址的别名呢?从豆知识2可知它代表的是结构体t后面的一个byte内存,依次类 推,awData[9]就是这个结构体t后面第10个byte的内存。如果结构体t后面的N个byte内存都不被其他变量使用,那t后面的N个byte内 存就可视为数组awData的可用范围,即awData数组的长度为N个字节。也就是说,如果你能控制N的大小,你就能自由地控制数组awData的大 小,那如何在运行过程中动态地控制这个N呢?使用malloc。
1 T_TEST *ptTest = NULL; 2 int wLen = 10; /* Get 10-byte length */ 3 4 ptTest = (T_TEST*)malloc(sizeof(T_TEST) + wLen); /* Create the space for array */ 5 6 if(ptTest != NULL) 7 { 8 ptTest->awData[9] = 10; /* Operate the last byte of array */ 9 }
上例中就是通过malloc在内存的堆区域中申请了一块内存区域,这个区域由两部分组成,一个是T_TEST结构体的大小(1 byte),另一个就是数组的长度wLen。通过这个方法可以确保结构体后面的wLen个内存是不会被其他变量占用的,这样数组就可以安全地使用。当数组 用完以后,可以free释放该块内存,再根据具体要求修改wLen重新申请,这样这个数组的长度就变得可变了,这就是柔性数组,既不用担心数组定义过大而 浪费空间,又不会因为数组定义过小而发生越界操作。
再谈谈实际的应用场景,我开发的某个软件模块中有两个逻辑运算功能,于是我将可以对这个模块的数据结构做如下定义
1 typedef _LOGIC_ 2 { 3 int wLogicOp; /* Logic operation type: AND/OR/XOR */ 4 int wLogicInputA; /* Logic input value A */ 5 int wLogicInputB; /* Logic input value B */ 6 }T_LOGIC; 7 8 typedef _MODULE_FUNC_ 9 { 10 int wFuncEn; /* Enable or disable logic func */ 11 int wLogicOutput; /* Output operation of logic */ 12 T_LOGIC tLogic1; /* Logic func 1 */ 13 T_LOGIC tLogic2; /* Logic func 2 */ 14 }T_MODULE_FUNC;
结构体T_LOGIC定义了逻辑运算的基本单元,包含了逻辑运算的操作(与或非等)和逻辑输入,而结构体T_MODULE_FUNC则是软件模块所定义的 数据结构,可以看出软件模块定义了两个逻辑运算基本单元tLogic1和tLogic2,tLogic1的运算结果将作为tLogic2的输入继续执行下 一级逻辑运算。如果说产品的需求就是两级的逻辑运算,那这个软件模块是可以满足要求,但如果说产品需求是三级逻辑运算,那原有的数据结构就不适用,代码也 要改,则模块的复用性就差,所以不能定义个tLogic3来解决该问题。为了解决这个问题,我们可以把原来的结构修改成这样:
1 typedef _MODULE_FUNC_ 2 { 3 int wFuncEn; /* Enable or disable logic func */ 4 int wLogicOutput; /* Output operation of logic */ 5 T_LOGIC tLogic[NUM_OF_LOGIC]; /* Logic unit */ 6 }T_MODULE_FUNC;
这样软件模块通过宏定义NUM_OF_LOGIC的数值就可以我需要多少级的逻辑运算,这种方法实现比较简单,也易于理解,但有时候在整体代码框架下这个 宏定义的位置需要考虑清楚,同时要关心头文件的包含顺序。为了让模块与其它模块更少耦合,让模块更好用,可以在这里使用柔性数组:
1 typedef _MODULE_FUNC_ 2 { 3 int wFuncEn; /* Enable or disable logic func */ 4 int wLogicOutput; /* Output operation of logic */ 5 T_LOGIC tLogic[0]; /* Logic unit */ 6 }T_MODULE_FUNC;
这样就可以不用事先定义这个NUM_OF_LOGIC宏定义,而在运行过程中动态的申请和使用,是不是很方便呢(其实很多情况下还是宏定义方便,呵呵)。
终于还是写完了,这篇写了好长时间,有了宝宝之后属于自己的时间少且零散,劝你还是在萌萌降临之前多多学习,我也尽可能的多总结点东西给你,希望你能收的到。
写在最后:借个地方向你感叹一下生活,你以后某天会放弃一个多年的向往,很多时候你会面临抉择,哪一个选项都伴随着牺牲,你自己要考虑清楚什么更重要。当
你遇到这样的纠结时,安慰自己,根本就没有那个选择,你在做的是你认为最重要的。我不想干预你太多想法,但记住一点,你有家庭的责任,请以整个家庭的角度
考虑问题。
标签:
原文地址:http://www.cnblogs.com/ianhom/p/4517704.html