分段列表:就是把分散的代码或数据,通过编译器编译链接把它们拼接起来放到一段连续空间内,如果这些分散的数据都是同一类型,则这个列表就是数组列表,我们程序可以通过数组方式访问这些数据。
实现分段列表应用场合用在哪里以及它有什么好处呢?下面拿设备启动时对各个模块进行初始化作为例子,逐一介绍三种实现方式:传统方式、列表方式、分段列表方式。
1、传统方式
在main()函数最前在分别调用这些初始化函数,当新增一模块就必须在main()增加代码,这种方式与main()过于耦合。试想一下linux系统,少则几百多则上千上万个初始化函数放在一起,那是多么恐怖的事,而且linux核心源文件是不给程序员随便改动的,否则会影响系统稳定性。也就是说对于中大型工程这种方式不可取。
intmain(void){//每当增加模块初始化,都需在这里增加初始化函数init_a();init_b();init_c();while(1){...}}2、列表方式
做一个数组列表,在列表加入这些初始化函数,main()的上电初始化时用for循环统一初始化,当新增一模块无须动代码,只需要在数组列表增加内容即可,这种方式比传统方式简洁方便很多。而且这个表格可以独立放到一个文件里,这样有效阻隔核心代码不被程序员改动。但当表格过多和表员过多时,维护及添加也是一件繁琐的事,容易出错或疏漏。
typedefvoid(*func_t)(void);constfunc_tfunc_tab[]={init_a,init_b,init_c};//平时只需维护此列表intmain(void){inti;for(i=0;isizeof(func_tab)/sizeof(func_tab[0]);i++){(*(func_tab[i]))();}while(1){...}}3、分段列表方式
在main()的上电初始化时用for循环调用事先预设好的数组列表,而这个数组列表内容(表员/组员)分散在各种模块中,依靠编译器编译链接时把它们存到这个数组列表里(无需人工添加),这就是分段列表:关键字-属性__attribute__;关键字-分段section。语法:
__attribute__((used,__section__(".name.""tail")))
.name.为分段名称,同名分段数据存放到同一段连接的空间内。tail为分段名称后缀,先由它决定数据先后顺序,再由编译代码先后顺序决定。used向编译器说明这段代码是有用的,即使在没有用到的情况下编译器也不会警告;而unused则是表示该函数或变量可能不使用,这个属性可以避免编译器产生警告信息。
主程序:main.h
typedefvoid(*p_init_fun_t)(void);structinit_fun_tab{p_init_func_tpfun;constuint8_t*name;};//属性关键字分段关键字分段名称分段名称后缀//+-----+-----++----+----++---+---++-+-+#defineINITSECTION(level)__attribute__((used,__section__(".init_fn."level)))#defineINIT_FRONT_EXPORT(func,name)INITSECTION("0.end")conststructinit_fun_tabinit_fn_##func={func,name}//分段列表开始#defineINIT_TABLE_EXPORT(func,name)INITSECTION("1")conststructinit_fun_tabinit_fn_##func={func,name}#defineINIT_LIMIT_EXPORT(func,name)INITSECTION("1.end")conststructinit_fun_tabinit_fn_##func={func,name}//分段列表结束//+---------+-------++----------------------------+------------------------+//分段列表宏一个表员(结构体)数据
主程序:main.c
voidtab_front(void){printf("tab_frontcode\r\n");}INIT_FRONT_EXPORT(tab_front,"tab_front()");//分段列表开始voidtab_limit(void){printf("tab_limitcode\r\n");}INIT_LIMIT_EXPORT(tab_limit,"tab_limit()");//分段列表结束intmain(void){conststructinit_fun_tab*p_init;//执行分段列表(由编译器维护列表内容)for(p_init=init_fn_tab_front+1;p_initinit_fn_tab_limit;p_init++){printf("run:%s\r\n",p_init-name);(*(p_init-pfun))();}while(1){...}}
模块a:
include"main.h"voidtest(void){printf("testcode\r\n");}INIT_TABLE_EXPORT(test,"test()");//告知编译器将函数地址加入到分段列表中
模块b:
include"main.h"voiddemo(void){printf("democode\r\n");}INIT_TABLE_EXPORT(demo,"demo()");//告知编译器将函数地址加入到分段列表中
main.c写好后,就不会改动,如果模块想在main.c完成初始化工作,只需在其初始化函数后面加入INIT_TABLE_EXPORT()宏,编译器编译时就可以通过此宏知道将函数指针加入到main.c的初始化列表。从代码上看,模块与模块之间没有任何的函数调用关系,高质量实现高内聚性低耦合度。
扩展分段列表除了可以用在初始化上,还可以用到好多应用场合。例如本人编写独立模块时,喜欢通过回调函数的方式来通知其它模块,而回调函数则是做成列表方式,即回调列表通知/触发多个模块。拿按键模块来说,当检测到有按键动作时,调用回调列表触发界面模块、恢复出厂初始化模块执行相应动作。
按键模块:key.h
typedefvoid(*p_key_fun_t)(uint8_tkey,uint8_taction,uint32_ttime);structkey_fun_tab{p_key_func_tpfun;};//属性关键字分段关键字分段名称分段名称后缀//+-----+-----++----+----++--+--++-+-+#defineKEYSECTION(level)__attribute__((used,__section__(".key_fn."level)))#defineKEY_FRONT_EXPORT(func)KEYSECTION("0.end")conststructkey_fun_tabkey_fn_##func={func}//分段列表开始#defineKEY_TABLE_EXPORT(func)KEYSECTION("1")conststructkey_fun_tabkey_fn_##func={func}#defineKEY_LIMIT_EXPORT(func)KEYSECTION("1.end")conststructkey_fun_tabkey_fn_##func={func}//分段列表结束//+---------+-------++---------------------+---------------------+//分段列表宏一个表员(结构体)数据
按键模块:key.c
voidktab_front(uint8_tkey,uint8_taction,uint32_ttime){}voidktab_limit(uint8_tkey,uint8_taction,uint32_ttime){}KEY_FRONT_EXPORT(ktab_front);//分段列表开始KEY_LIMIT_EXPORT(ktab_limit);//分段列表结束voidkey_scan(void){...conststructkey_fun_tab*p_key;//按键有动作时回调列表通知其它模块(由编译器维护列表内容)for(p_key=key_fn_ktab_front+1;p_keykey_fn_ktab_limit;p_key++){(*(p_key-pfun))(key,action,time);}...}
界面模块:
include"key.h"voidlcd_key_callback(uint8_tkey,uint8_taction,uint32_ttime){if((key=KEYNO_K1)(key=KEYNO_K4)){key-=KEYNO_K1;if((action==KEYAT_1CLICK/*单击*/)
(action==KEYAT_2CLICK/*双击*/)){if(time==0)/*短按,非长按*/{....//收到按键单/双击动作}}}}KEY_TABLE_EXPORT(lcd_key_callback);//告知编译器将函数地址加入到分段列表中
恢复出厂初始化模块:
include"key.h"voidfrst_key_callback(uint8_tkey,uint8_taction,uint32_ttime){if(key==KEYNO_FRST){if(action==KEYAT_1CLICK/*单击*/){if(time==)/*长按5秒*/{....//收到按键长按5秒动作}}}}KEY_TABLE_EXPORT(frst_key_callback);//告知编译器将函数地址加入到分段列表中预览时标签不可点收录于话题#个上一篇下一篇