用C语言做开发,开发速度是快,但写出的代码稍不留心,就做成一个大胖子了.在嵌入式应用中,有时很关注成本与性价比,往往希望能在性价比上找到平衡点,代码尺寸的优化就显得很重要了.
当然,针对熟悉的8位51单片机,大家都知道的方法和原则我先列几个,不在讨论之列
俺常用的瘦身密绝
1.能用unsignedchar的不用unsingedint,依些类推到ulong
2.能定义到data空间的不用定义到idata,更不要定义到Xdata
3........
哈哈,大家补充.
网友评论:全局变量不要用到中断应该不会出现预想不到的效果.
网友评论:有时代码速度和代码空间是能二者兼得的.俺就遇到过一类段代码,贴出来给大家看看.
下面是新的代码,采用联合体拆解数据.
uint8SD_read_sector(uint32addr,uint8xdata*Buffer)
//****************************************************************************
{
uint8idataCMD[6];
unionSDSector
{
uint32ulAddress;
uint8ucADDByte[4];
}CurrentSDSector;
CMD[0]=0X51;
CMD[4]=0X00;
CMD[5]=0XFF;
addr=addr<<9;//addr=addr*512
CurrentSDSector.ulAddress=addr;
CMD[1]=CurrentSDSector.ucADDByte[0];
CMD[2]=CurrentSDSector.ucADDByte[1];
CMD[3]=CurrentSDSector.ucADDByte[2];
returnSD_Read_Block(CMD,Buffer);
}
下面是老的代码,用的是程序变换,不仅占空间,而且费时间,但看起来较容易理解,可读性貌视很好.
uint8SD_read_sector(uint32addr,uint8*Buffer)
//****************************************************************************
{
uint8CMD[]={0x51,0x00,0x00,0x00,0x00,0xFF};
addr=addr<<9;//addr=addr*512
CMD[1]=((addr&0xFF000000)>>24);
CMD[2]=((addr&0x00FF0000)>>16);
CMD[3]=((addr&0x0000FF00)>>8);
returnSD_Read_Block(CMD,Buffer,512);
}
网友评论:比如32位arm,uint(32bit)自增自减比uchar自增自减快,因为uchar是uint做自增自减再做位屏蔽得到的
网友评论:但必须设置好访问原则,否则很容易出乱
网友评论:首先说明,数组名并不是指针.所以也就不能说什么为什么把数组和指针独立起来的问题了.数组名的内涵在于它所指向的实体是一种数据结构,而这种数据结构就是--数组.数组名的外延在于它可以转换为指向它所指代的实体的指针,而且是一个常量指针(ps:11楼).而且我们常说的指向数组的指针又是另一种变量类型了,它仅仅意味着数组存放的地址而已.
另外需要格外注意的是:数组名作为函数形参时,在函数体内,它失去了本身的内涵,彻底成为一个指针,并且它也失去了他的常量特性(也就是说,它可以自加,自减,可以被修改).(我们不能总是相信教科书上的东西,学校只能教我们要怎么做,具体怎么做要自己学习)
20楼的说法是对的,为了避免作用域的无限膨胀,我们要在妥善处理信息的同时尽量缩短变量的生存周期.而且,这也是为了便于维护和方便别人阅读代码.在C++中更要注意这样的问题.
网友评论:良好的结构、可维护性>..效率>.....实现难度>.>.....................................................................................................................................................................代码优化
大部分情况,代码优化是由编译器去做的事情,换个编译器多块好省;
还有部分情况,是由单片机厂商做的事情,加点FLASH不值什么钱;
网友评论:编译器有时对愚蠢的(换言之,不太高效)的程序写法是无能为力的.
网友评论:指针对元素的操作,尤其是遍历操作非常高效.
俺猜想可能是指针编译时使用了高效的汇编寻址中的基址变址来实现
内存操作有关.
数组单元的寻址应变是基地址+偏移量为每一个数组单元的访问寻址的吧.
网友评论:别老拿单片机说事,量大怎么怎么样,真正有多少人能做称的上量的东西?大部分电工做的东西能上多少量,能到100psc不?
就大部分东西来说(坛子里讨论的那些),一个单片机能占成本的比例很小。有时连个接插件都比不上,为几毛钱把自己折腾的死去活来,何必呢。
非常赞同3L,做大事不拘小节。
网友评论:事实上,更小的代码未必就是没有了可读性!
很多地方还得对编译器特性细节的把握.
比如关于指针和数组的问题.
在KEILC下,可是数组的代码效率高于指针.
这么好的论题,怎么没有看见两位高手:dengm和hotpower参加呢?
网友评论:在函数中使用多个指针变量,往往编译出来代码量更大!
网友评论:使用switchcase时,ifelse判断复杂条件时,应该尽量增加冗余代码,提高几个判断条件的相似度。可以类比数字逻辑电路的卡诺图优化。
网友评论:如果用好的算法来查表,就不说了。
对于用简单查表,就没有switch好,尤其是当case很多的情况下,尤其是case值离散的情况下。
网友评论:51单片机内存就有好几种,有dataidatapdataxdatacode,通用指针需要对所有情况都做兼容,效率是会打折扣.
网友评论:良好的架构;合理的类型声明;指针的正确使用;最优的算法实现;任务的分配;对内存使用的正确理解;另外相矛盾的全局变量,短地址最好,但是对实现程序良好和封装和移植却矛盾。。。。看做产品吧
网友评论:在c文件中被调用函数写在前面而不再写函数声明,编译出来的代码量会小一点。不知新版的keil是否也这样?
网友评论:倒是在没有参数的地方用void明确化更有效
Demo:
voidmain(void)
{
}
网友评论:刚拿出N年前写的跑马
灯demo程序,把主函数main放到文件最后面,这样,其所调用的函数都在其前面定义编译后data=20.0,code=2318。
再做一个实验,把主函数main放到文件的所有函数定义的前面,main中调用的函数在其后定义,文件头部写上这些函数的原型,编译后data=20.0,code=2326。
前后对比,代码量少了8bytes!用keilv8.05a编译的。
网友评论:很多细节的地方都会影响到C51的code大小。对于无需传递函数或无需返回值的函数使用void关键字,也是一个好习惯
网友评论:你说的情况也可能代码变大,只是你运气好,是因为程序中有很多跳转指令,KEIL自动优化成SJMP,AJMP,LJMP.
网友评论:学习学习
同意楼主的想法
有时候我们会为了达到目的
而忽略的代码的大小
网友评论:具体原因没有仔细研究,但代码中没有查表,也没有goto语句,纯粹是while(1)中几个函数顺序调用,展现不同的流水灯效果。
网友评论:我也发现这个问题,即使在8.X里面
网友评论:顺序:
正确性,
健壮性,
可读性,
最后才是代码尺寸何运行速度
网友评论:同意LS,没有保证正确性、稳定性、可读性及可测性之前,
所谓代码大小都是浮云
网友评论:比如以下函数,用C51写与卡交换数据时多半要用到,如何优化,希望大家提出自己的想法.
unsignedlongSwapINT32(unsignedlongdData)
{
dData=((dData&0xff)<<24)|((dData&0xff00)<<8)|((dData&0xff000000)>>24)|((dData&0xff0000)>>8);
returndData;
}
网友评论:其实写程序,看着是写代码,
实际上是:
抽象现实问题为逻辑表达,
有的时候为了所谓的效率,复用,
过渡追求代码大小,运行效率
反而会造成过渡抽象,结果会隐含错误.
其实写程序,前人总结的无非是:
高内聚,低耦合,模块功能要单纯,
模块内部紧密联系,
外部出入口条件要少,
完成一个单纯的功能.
网友评论:程序结构--代码复用算法--专用算法代替通用算法
专用法代替通用算法就把可移植给弄死了.
个人觉得还是向着"高内聚,低耦合,模块功能要单纯"走要好.碰上
PIC16C54这样的不如直接上汇编算了.
再碰上
PIC16F73这样的,先按上面的走,到时装不下了,找占用最大的下手,如果还差较多就直接换芯片得了,谁知到时要加个小功能,就整死了,不如干脆直接换上大的.
所以现在搞的就很喜欢ATMEGA16与ATMEGA32这样的芯片用,小的不行,后面还有个大的等着你用.
硬件电路一点也不用改
网友评论:就跟玩游戏一样,先把等级提高了,到时武器再慢慢去练的好.要不然再好的武器装备在新人身上也发挥不了大功效,反而伤了自已可就麻烦了.
坛内新人还是较多,期待楼上及楼上高人开坛传授码程序如何做到"高内聚,低耦合,模块功能要单纯"及硬件无关大法.
网友评论:个人认为硬件无关大法就用OS
网友评论:学习
网友评论:unsignedlongSwapINT32(unsignedlongdData)
{
unionBigLongValue
{
ulongulReturnValue;
ucharucByte[4];
}CurrentBigLongValue;
uchar*ucData;
ucData=(uchar*)&dData;
CurrentBigLongValue.ucByte[3]=*ucData++;
CurrentBigLongValue.ucByte[2]=*ucData++;
CurrentBigLongValue.ucByte[1]=*ucData++;
CurrentBigLongValue.ucByte[0]=*ucData++;
returnCurrentBigLongValue.ulReturnValue;
}
此函数虽然节省空间,但与编译器对整型变量的大小端约定关联,故移植时尤其要注意编译坏境的约定.
网友评论:现在的重点是优化代码,其它的肯定也会考虑的
比如以下函数,用C51写与卡交换数据时多半要用到,如何优化,希望大家提出自己的想法.
unsignedlongSwapINT32(unsignedlongdData)
{
dData=((dData&0xff)<<24)|((dData&0xff00)<<8)|((dData&0xff000000)>>24)|((dData&0xff0000)>>8);
returndData;
}
网友评论:7楼你好:青椒一下为什么计数器尽量用减法啊
网友评论:减法运算会生成DJNZ指令,效率高.
网友评论:unsignedlongSwapINT32(unsignedlongdData)
{
#if0
dData=((dData&0xff)<<24)|((dData&0xff00)<<8)|
((dData&0xff000000)>>24)|((dData&0xff0000)>>8);
#else
unsignedlongtemp=dData;
unsignedchar*P=&temp;
((unsignedchar*)&dData)[0]=((unsignedchar*)&temp[3];
((unsignedchar*)&dData)[1]=((unsignedchar*)&temp[2];
((unsignedchar*)&dData)[2]=((unsignedchar*)&temp[1];
((unsignedchar*)&dData)[3]=((unsignedchar*)&temp[0];
#endif
returndData;
}
这种写法可以节省68字节,应该还有更好的,期待能有更好的出来!
网友评论:unsignedlongSwapINT32(u32dData)
{
//dData=((dData&0xff)<<24)|((dData&0xff00)<<8)|
//((dData&0xff000000)>>24)|((dData&0xff0000)>>8);
unsignedcharbuf,buf1;
buf=((unsignedchar*)&dData)[1];
buf1=((unsignedchar*)&dData)[0];
((unsignedchar*)&dData)[0]=((unsignedchar*)&dDat[3];
((unsignedchar*)&dData)[1]=((unsignedchar*)&dDat[2];
((unsignedchar*)&dData)[2]=buf;
((unsignedchar*)&dData)[3]=buf1;
}
比最原始的写法节省94个字节,编译平台KEILC51,期待更好的!
网友评论:俺来试试效果,挑战自我的精神值得表扬呀!
网友评论:95:unsignedlongSwapINT32(unsignedlongdData)
C:0x076C8F61MOV0x61,R7
C:0x076E8E60MOV0x60,R6
C:0x07708D5FMOV0x5F,R5
C:0x07728C5EMOV?_PRINTF517?BYTE(0x5E),R4
100:unsignedcharbuf,buf1;
101:
102:buf=((unsignedchar*)&dData)[1];
C:0x0774AF5FMOVR7,0x5F
103:buf1=((unsignedchar*)&dData)[0];
104:
C:0x0776AE5EMOVR6,?_PRINTF517?BYTE(0x5E)
105:((unsignedchar*)&dData)[0]=((unsignedchar*)&dData)[3];
C:0x077885615EMOV?_PRINTF517?BYTE(0x5E),0x61
106:((unsignedchar*)&dData)[1]=((unsignedchar*)&dData)[2];
C:0x077B85605FMOV0x5F,0x60
107:((unsignedchar*)&dData)[2]=buf;
C:0x077E8F60MOV0x60,R7
108:((unsignedchar*)&dData)[3]=buf1;
109:
C:0x07808E61MOV0x61,R6
110:}
111:#endif
112:
C:0x078222RET
网友评论:包含返回代码部分,用C要想高效还是传递指针最好,可以通过指针直接修改原始数据,节省了大量的数据传递时间,那样应该和汇编效率不相上下.
原代码
unsignedlongSwapINT32(unsignedlongdData)
{
//dData=((dData&0xff)<<24)|((dData&0xff00)<<8)|
//((dData&0xff000000)>>24)|((dData&0xff0000)>>8);
union{
U8b[4];
U32x;
}temp;
temp.b[3]=((U8*)&dData)[0];
temp.b[2]=((U8*)&dData)[1];
temp.b[1]=((U8*)&dData)[2];
temp.b[0]=((U8*)&dData)[3];
returntemp.x;
}
编译效果
;#defineU8unsignedchar
;#defineU16unsignedint
;#defineU32unsignedlong
;
;
;unsignedlongSwapINT32(unsignedlongdData)
RSEG?PR?_SwapINT32?MAIN
_SwapINT32:
USING0
;SOURCELINE#130
MOVdData?141+03H,R7
MOVdData?141+02H,R6
MOVdData?141+01H,R5
MOVdData?141,R4
;{
;SOURCELINE#131
;//dData=((dData&0xff)<<24)|((dData&0xff00)<<8)|
;//((dData&0xff000000)>>24)|((dData&0xff0000)>>8);
;
;union{
;U8b[4];
;U32x;
;}temp;
;
;temp.b[3]=((U8*)&dData)[0];
;SOURCELINE#140
MOVtemp?142+03H,dData?141
;temp.b[2]=((U8*)&dData)[1];
;SOURCELINE#141
MOVtemp?142+02H,dData?141+01H
;temp.b[1]=((U8*)&dData)[2];
;SOURCELINE#142
MOVtemp?142+01H,dData?141+02H
;temp.b[0]=((U8*)&dData)[3];
;SOURCELINE#143
MOVtemp?142,dData?141+03H
;
;returntemp.x;
;SOURCELINE#145
MOVR7,temp?142+03H
MOVR6,temp?142+02H
MOVR5,temp?142+01H
MOVR4,temp?142
;}
;SOURCELINE#146
?C0020:
RET
;ENDOF_SwapINT32
END
网友评论:ayb_ICE的测试结果如图
网友评论: 相关链接:/upfiles/img/20097/2009712104759742.rar
网友评论:函数完成的功能实际上就是将入口处R4.R5,R6,R7的内容变换成R7,R6,R5,R4然后返回上层主调函数
理论上最优化的伪码为
MOVACC,R4
XCHR7,ACC
MOVACC,R6
XCHR5,ACC
RET
如何引导Keil编译器使用XCH指令,不得其解.
网友评论:74L的没有写返回语句,所有小了4条汇编指令语句,用C不可能写出交换
寄存器的语句
理由如下:
如果对某个局部变量进行强制指针变换,那么这个变量不可能被分配给寄存器,因为寄存器的取决于当前的寄存器组,而这个函数要想高效必须进行指针变换.
实际是这个函数的功能不复杂,语句也少,要想用C写最好传递一个或两个具体指针参数,然后利用指针进行数据交换,不用返回数据,传递具体指针也比传递U32数据效率要高,局部变量也可以被分配给寄存器,这样效率是很高的.指针方式也比用汇编的参数传递和返回参数方式要高效的多.此处用于传递参数的时间已经超过了数据处理的核心时间了...
网友评论:源代码
#defineU32unsignedlong
#defineU8unsignedchar
#definemacro_SwapINT32(src,des)
{
((U8*)&src)[0]=((U8*)&des)[3];
((U8*)&src)[1]=((U8*)&des)[2];
((U8*)&src)[2]=((U8*)&des)[1];
((U8*)&src)[3]=((U8*)&des)[0];
}
voidtest(void)
{
U32x,y;
macro_SwapINT32(x,y);
}
编译效果
;#definemacro_SwapINT32(src,des)
;{
;((U8*)&src)[0]=((U8*)&des)[3];
;((U8*)&src)[1]=((U8*)&des)[2];
;((U8*)&src)[2]=((U8*)&des)[1];
;((U8*)&src)[3]=((U8*)&des)[0];
;}
;
;voidtest(void)
RSEG?PR?test?MAIN
test:
USING0
;SOURCELINE#106
;{
;SOURCELINE#107
;U32x,y;
;SOURCELINE#108
;
;macro_SwapINT32(x,y);
;SOURCELINE#110
MOVx?141,y?142+03H
MOVx?141+01H,y?142+02H
MOVx?141+02H,y?142+01H
MOVx?141+03H,y?142
;}
;SOURCELINE#111
RET
;ENDOFtest
END
网友评论:abc-ice有81楼提出的宏变换的确是高效而又简洁的风格,值得大家学习.
网友评论:显得不够聪明,明显的字节返回值在R7里是不合理的做法,因为对返回值的判断时还是要把R7赋给ACC,如果直通过ACC传递返回效率会高很多,本身ACC的效率就比RX要高,这些可能是历史原因造成的.
网友评论:看了这么多,学到不少东西。继续学习!
网友评论:如果函数返回值是一个uchar,的确应该用ACC返回.还好位变量是用进位位C返回,效率较高.
印象中Keil早期也是给人做OEM生产的.
网友评论:谢谢大家各抒已见
从宏观上提出的办法网友已给出了最佳答案
1.程序结构--代码复用
2.算法--专用算法代替通用算法
从微观上来解决,很多网友已给出了速度又快,代码尺寸又小的演示程序,只要用心优化,总能做得更好!
哈哈,如果大家喜欢,可以开展下一个议题.
网友评论:1:能否把“高内聚,低耦合,模块功能要单纯”这个说法做个详细的阐述呢?
2:宏观上,程序结构--代码复用;算法--专用算法代替通用算法。能再细致的总结一下吗?怎么个代码复用,什么样的是专用算法,什么样的是通用算法。
3:可不可以将微观上的代码优化方法把大家讨论的做个总结呢?让我这个莱鸟也能享受一下前辈们的劳动成果呵呵。。。。
我是莱鸟,问题有点菜,不要笑我哦!期待中
网友评论:还是小点好,特别是低价位的