C++的优化汇编代码

合集下载

使用gcc和glibc来优化程序 转载

使用gcc和glibc来优化程序  转载

使用gcc和glibc来优化程序转载使用gcc和glibc来优化程序(转载)2011-01-12 17:38Optimize Applications with gcc and glibc by Ulrich Drepper 1.介绍===本文总结一些关于代码优化的经验,这些经验是不完整的.本文不是讨论编译器如何优化代码,后者是完全不同的另外一个领域.2.编译时优化(Using Optimizations Performed at Compile-Time)=====2.1消除无用代码(Dead Code Elimination)Dead Code指永远不会执行的代码.例如:long int add(long int a,void*ptr,int type){if(type==0)return a+*(int*)ptr;else return a+*(long int*)ptr;}这个函数根据type的值来判断ptr的类型,从而求和.优化1:多数情况下int和long int是相同的,因此可以优化为long int add(long int a,void*ptr,int type){if(sizeof(int)==sizeof(long int)||(type==0))return a+*(int*)ptr;else return a+*(long int*)ptr;}sizeof运算总是在编译时进行,因此增加的条件表达式总是在编译时计算. 如果long int和int确实相同,那么这个函数就可以被编译器优化.进一步优化,利用limits.h中定义的宏#include limits.h long int add(long int a,void*ptr,int type){#if LONG_MAX!=INT_MAX if(type==0)return a+*(int*)ptr;else#endif return a+*(long int*)ptr;}这样,即便在long int不同于int的平台上,该函数也被优化了2.2节省函数调用(Saving Function Calls)很多函数很短小,相对函数执行的时间,函数调用的代价不可忽视.例如标准库中的字符串函数和数学函数.解决办法有两个:使用宏代替函数,或者用inline函数.一般而言,inline函数和宏一样快,但是更安全.但是如果用到alloca和__builtin_constant_p的时候,可能要考虑用优先使用宏了但是,如果函数被声明为extern,inline并不总是有效了.另外,当gcc的编译优化选项没有打开时,gcc不会展开inline函数.如果inline函数是static的,那么编译器总是会展开该函数,不考虑是否真的值得.尤其是当使用-Os(optimize for space)选项时,static inline 函数是否值得使用就是个问题了.编写正确而又安全的宏并不容易.要注意a)正确使用括号括起参数,例如#define mult(a,b)(a*b)//错误#define mult(a,b)((a)*(b))b)宏定义中的大括号引入新的block,这有时侯会导致问题.例如#define scale(result,a,b,c)\{\int c__=(c);\*(result)=(a)*c__+(b)*c__;\}下面的代码编译会出现问题:if(.)scale(r,a,b,c);///多余的分号导致编译错误else else{}正确的写法应该是:#define scale(result,a,b,c)\do{\int c__=(c);\*(result)=(a)*c__+(b)*c__;\}while(0)c)如果参数是表达式并且在宏定义中出现多次,尽量避免重复计算.这也是上面例子中要引入变量c__的原因.但这会限制变量c__的类型.d)宏缺乏返回值2.3编译器内部函数(Compiler Intrinsics)绝大部分C编译器都知道内部函数(Intrinsic functions).它们是特殊的inline函数,由编译器提供使用.这些函数用外部实现来代替.gcc2.96的内部函数有*__builtin_alloca:动态分配栈上内存dynamiclly allocate memory on the stack*__builtin_ffs:find first bit set*__builtin_abs,__builtin_labs:absolute value of an integer*__builtin_fabs,__builtin_fabsf,__builtin_fabsl absolute value of floating-point vlaue*__builtin_memcpy copy memory region*__builtin_memset set memory region to give value*__builtin_memcmp compare memory region*__builtin_strcmp*__builtin_strcpy*__builtin_strlen*__builtin_sqrt,__builtin_sqrtf,__builtin_sqrtl*__builtin_sin,__builtin_sinf,__builtin_sinl*__builtin_cos,__builtin_cosf,__builtin_cosl*__builtin_div,__builtin_ldiv integer division with rest*__builtin_fmod,__builtin_frem module and remainder of floating-point value不能保证所有内部函数在所有平台上都定义了.关于intrinsic function,有一个很有用的特性:如果参数在编译时是常数,那么可以在编译时计算其值.例如strlen("foo bar")有可能在编译时就计算好.2.4 __builtin_constant_p __builtin_constant_p并不属于intrinsic function,它是一个类似于sizeof的操作符.__builtin_constant_p接收一个参数,如果该参数在运行时是固定不变的(constant at runtime),那么就返回非0值,表示这是一个常量.例如,前面的add函数可以在进一步优化:#define add(a,ptr,type)\(__extension__\(__buildtin_constant_p(type)\?((a)+((type)==0\?*(int*)(ptr):\*(long int*)(ptr)))\:add(a,ptr,type)))如果第三个参数为constant,那么这个宏将改变add函数的行为;否则就调用真正的add函数.这样尽量在编译时计算,从而提高了效率.2.5 type-generic macro有时侯我们希望宏对不同的参数数据类型,能正确处理不同数据类型并表现相同的行为,可以借助__typeof__例如前面的scale#define tgscale(result,a,b,c)\do{\__externsion__ __typeof__((a)+(b)+(c))c__=(c);\*(result)=(a)*c__+(b)*c__;\}while(0)这里,c__自动拥有返回值类型,而不是前面固定写的int类型.__typeof__(o)定义了与o相同的类型.__typeof__的另外一个用途:被ISO C9x用于tgmath中,从而实现一些对任意数据类型(包括复数)都适用的数学函数.错误示例:#define sin(val)\(sizeof(__real__(val))sizeof(double)?\(sizeof(__real__(val))==sizeof(val)?\sinl(val):csinl(val))\:(sizeof(__real__(val))==sizeof(double)?\(sizeof(__real__(val))==sizeof(val)?\:sin(val):csin(val))\:(sizeof(__real__(val))==sizeof(val)?\sinf(val):csinf(val))))上面这个宏的意思是:如果val是虚数(即sizeof(__real__(val))!=sizeof(val)),那么对val调用csinl,csin和csinf如果val是实数,且比double精度高,即sizeof(__real__(val))sizeof(double)),那么对val调用sinl,就long double,否则调用sin或者sinf.sinl:相当于sin(long double)sin:相当于sin(double)sinf:相当于sin(float)csin:对应的复数sin函数但是这个宏是有错误的,由于整个宏是一个表达式,表达式是有静态的类型的,能代表该表达式的数据类型必须有足够的精度来表示各种值, 所以这个表达式的最终数据类型就是comple long double,这并不是我们期望的.正确的实现方法是:#define sin(val)\(__extension__\({__typeof__(val)__tgmres;\if(sizeof(__real__(val))sizeof(double))\{\if(sizeof(__real__(val))==sizeof(val))\__tgmres=sinl(val);\else\__tgmres=csinl(val);\}\else if(sizeof(__real__(val))==sizeof(double))\ {\if(sizeof(__real__(val))==sizeof(val))\__tgmres=sin(val);\else\__tgmres=csin(val);\}\else\{\if(sizeof(__real__(val))==sizeof(val))\__tgmres=sinf(val);\else\__tgmres=csinf(val);\}\__tgmres;}))上面对__tgmres赋值的6个分支中,真正会执行的那个分支是不存在精度损失的;其他分支都会作为deadcode被编译器优化掉3.help the compiler==GNU C编译器提供一些扩展来更清晰的描述程序,从而帮助编译器生成代码.3.1不返回的函数(Functions of No Return)大项目一般都至少有一个用于严重错误处理的函数,这个函数体面的结束应用程序.这个函数一般情况下不会被编译器优化,因为编译器不知道它不返回.例如:void fatal(.)__attribute__((__noreturn__));void fatal(.){//print some message exit(1);}//application code{if(d==0)fatal(.);else a=b/d;}函数fatal保证不会返回,exit函数也不返回.因此可以在函数原型上加上__attribute__((__noreturn__)).如果没有noreturn的标记,gcc会把上面的代码翻译成下面的形式(伪代码):1)compare dwith zero 2)if not zero jump to 5)3)call fatal 4)jump to 6)5)compute b/d and assign to a6).如果有noreturn标记,gcc可以优化代码,省略4).对应的源代码为{if(d==0)fatal(.);a=b/d;}3.2常值函数(constant value functions)有些函数的值仅仅取决于传入的参数,这种函数没有副作用,我们称之为pure function.对于相同的参数,这种函数有相同的返回值.举例说明:htons函数要么返回参数(如果是big-endian计算机),要么交换字节顺序(如果计算机是little-endian).这个函数没有副作用,是一个pure function.那么下面的代码可以被优化:{short int server=.while(1){struct sockaddr_in s_in;memset(&s_in,0,sizeof s_in);s_in.sin_port=htons(serv);.}}优化后的结果为:{short int server=.serv=htons(serv);while(1){struct sockaddr_in s_in;memset(&s_in,0,sizeof s_in);s_in.sin_port=serv;.}}从而减少循环中执行的代码,节省CPU.但是编译器并无法知道函数是否是pure function.我们必须给pure function显著的标记:extern uint16_t htons(uint16_t __x)__attribute__((__const__));__const__可以用来标记pure function.3.3 Different Calling Conventions每种平台都支持特定的calling conventions以便由不同语言和编译器写的程序/库能够一起工作.但是,有时侯在某些平台上,编译器支持一种更高效的calling convention.在项目内部使用这种calling convention不会影响系统的其他部分.尤其是在Intel ia32平台上,编译器支持多种不同于标准Unix x86的calling convention,这有时侯会大大提高程序速度.GNU C编译器手册有更详细解释.本节只讨论x86平台.改变函数的calling convention的两个办法:1)命令行选项(command line option):这种方法不安全,所有函数(包括exported function)都受到影响2)对单个函数设置function attribute.3.3.1 __stdcall__一般情况下,函数参数是通过栈来传递的,因此需要在某个位置调整栈指针.ia32 unix平台上标准的calling convention是让调用方(caller)调整栈指针;因此可以延迟调整操作,一次同时调整多个函数的栈指针.如果函数被标记为__stdcall__,这意味这个函数自己调整栈指针.在ia32 平台上,这不算是坏注意,因为ia32体系结构提供一个指令,能同时从函数调用返回并调整栈指针.示例:int __attribute__((__stdcall__))add(int a,int b){return a+b;}int foo(int a){return add(a,42);}int bar(void){return foo(100);}上面的代码翻译成汇编大致如下:8 add:9 0000 8B 442408 movl 8(%esp),%eax 10 0004 03442404 addl 4(%esp),%eax 11 0008 C20800 ret.17 foo:18 0010 6A2A pushl 19 0012 FF 742408 pushl 8(%esp)20 0016 E8E5FFFF call add 20 FF 21 001b C3 ret.28 0020 6A64 pushl0 29 0022 E8E9FFFF call foo 29 FF 30 002783C404 addl,%esp 31 002a C3 ret从上面的例子可以看出,add函数被标记为__stdcall__,foo函数在调用add后直接返回,不需要调整栈指针,因为add函数已经调整来指针(ret指令完成返回和调整指针操作);而bar函数调用foo函数,调用结束后必须调整栈指针.由此可见,使用__stdcall__是有好处的;但是,现代编译器都已经很智能,能作到一次性为多个函数调用调整栈指针,从而使得生成的代码更少速度更快.此外,以后的发展可能会出现更快的调用方式,所以使用__stdcall__必须非常谨慎.3.3.2 __regparm__ __regparm__只能在ia32平台上使用,它能指明有多少个(最多3个)整数和指针参数是通过寄存器来传递的,而不是通过栈传递.当函数体比较短小,而且参数立刻就能使用时,这种方式效果很显著.假设有下面的例子:int __attribute__((__regparm__(3)))add(int a,int b){return a+b;}经过编译优化后,生成的代码时9 0000 01D0 addl%edx,%eax 10 0002 C3 ret这个代码比起3.3.1中add的代码更高效.用寄存器传参数总是很快.3.4 Sibling Calls经常有这样的代码:一个函数最后结束时是在调用另外一个函数.这种情况下生成的伪代码如下://this is in function f1 ncall function f2 n+1 execute code of f2 n+2 get return address from call in f1 n+3 jump back into function f1 n+4 optionally adjust stack pinter from call to f2 n+5 get return address from call to f1 n+6 jump back to caller of f1经过优化,f1在调用f2结束后可以直接返回.3.5使用goto goto有时侯提高效率4.了解库(Knowing the Libraries)==4.1 strcpy vs.memcpy strcpy:两个参数src和dest,逐个byte拷贝memcpy:三个参数,src,dest和size,按word拷贝strncpy:3个参数:src,dest和length退出条件:遇到NUL字符或达到拷贝长度逐个检查byte是否为NUL追加NUL字符非gcc内部函数memcpy:3个参数退出条件:达到拷贝长度按word检查长度不必追加NUL字符gcc内部函数,特殊优化类似的,mem*和对应的str*函数都存在差别.mem*函数参数多些,一般情况下这不是问题,可以通过寄存器传参数;但是当函数被inline的时候,寄存器可能不够,生成的代码可能稍微复杂一些.建议如下:*尽量别使用strncpy,而使用strcpy*如果要拷贝的字符串很短,用strcpy*如果字符串可能很长,用memcpy 4.2 strcat和strncat关于字符串操作的一个金口玉言(gold rule)是:绝对不要使用strcat和strncat.要使用这两个函数,必须知道长度,并准备足够的空间.定型代码如下:{char*buf=.;size_t bufmax=.;if(strlen(buf)+strlen(s)+1 bufmax)buf=(char*)realloc(buf,(bufmax*=2));strcat(buf,s);}上面的代码中,已经调用了strlen,strcat中会重复执行strlen 操作,因此更高效的作法是:{char*buf=.;size_t bufmax=.;size_t slen;size_t buflen;slen=strlen(s)+1;buflen=strlen(buf);if(buflen+slen bufmax)buf=(char*)realloc(buf,(bufmax*=2));memcpy(buf+buflen,s,slen);}4.3内存分配malloc和calloc:分配堆内存.alloca分配栈内存.malloc的实现:从内核申请内存,可能会调用sbrk系统调用;在某些系统上如果申请的内存很多,可能会调用mmap来分配内存.malloc的内部实现会用相关的数据结构来管理好申请内存,以便释放或者重新申请.因此调用malloc的代价并不低.alloca的实现相对简单得多,起码编译器能直接把它作为inline来编译,alloca只是简单修改一下栈指针就可以了.而且,调用alloca后不需要调用free函数来释放内存.free函数的代价也是不小的.但是,alloca申请的内存只能用在当前函数中,而且alloca不适合用来申请大量内存,很多平台系统出于安全考虑对栈的大小有限制.malloc的实现和内核相关,能更好的处理大内存申请.alloca总是成功的,因为它只是执行修改栈指针操作而已.因此alloca非常适合在函数内部申请局部使用的内存,不比检查申请释放成功,也不必调用free来释放内存,不仅提高性能还简化来代码.示例如下:int tmpcopy(const int*a,int a){int*tmp=(int*)malloc(n*sizeof(int));int_fast32_t count;int result;if(tmp==NULL)return-1;for(count=0;count n;++count)tmp[count]=a[count]^0xffffffff;result=foo(tmp,n);free(tmp);return result;}用alloca改良后的代码变简单了:省略了free和指针检查. int tmpcopy(const int*a,int a){int*tmp=(int*)alloca(n*sizeof(int));int_fast32_t count;for(count=0;count n;++count)tmp[count]=a[count]^0xffffffff;return foo(tmp,n);}GNU libc提供strdupa和strndupa,就是用来局部临时拷贝字符串.它们与strdup和strndup的不同就是,前者调用alloca,后者调用malloc.因此strdupa和strndupa只能是宏,而不能是函数.下面是strdupa的错误实现://Please note this is WRONG!#define strdupa(s)\(__extension__\({\__const char*__old=(s);\size_t __len=strlen(__old)+1;\(char*)memcpy(__builtin_alloca(__len),__old,__len);\}))上面的实现代码中,memcpy对alloca的调用是错误的,因为alloca修改了栈指针,而在某些系统上,传递给函数调用的参数也是放在栈上的,这会导致严重错误.所以,绝对不能在函数参数列表中调用alloca,也不能在参数列表中通过strdupa或其他方式隐式调用alloca.上面的代码被编译成这样的伪代码:1.push __len on the stack,change stack pointer2.push __old in the stack,change stack pointer3.modify stack pointer for newly allocated object4.push current stack pointer on stack,change stack pointer5.call memcpy正确代码应该是这样的://Duplicate S,returning an identical alloca'd string.#define strdupa(s)\(__extension__\({\__const char*__old=(s);\size_t __len=strlen(__old)+1;\char*__new=(char*)__builtin_alloca(__len);\(char*)memcpy(__new,__old,__len);\}))4.4其他内存相关问题1)realloc代价相当高,要执行malloc/memcpy/free三个操作2)malloc并不一定每次都向系统内核申请内存,它本身也管理了内存的申请和释放;释放的内存不一定还给系统,而是留给下次申请3)估算出程序需要的大致内存,减少申请和分配次数,可以提高效率4)ISO C定义了函数calloc,这个函数分配内存会全部用0填充,它比调用malloc和memset更高效,因为对于通过内核mmap得到内存已经被清0了,calloc 不会再执行清0操作;而对sbrk获得的内存,calloc会执行清0操作;从而节省不必要的清0操作.4.5用最合适的数据类型ISO C9x定义了一个重要的新头文件:stdint.h.其中定义了int_least8_t,uint_least8_t,int_least16_t,.int_least64_t等数据类型.int8_t确保正好有8bit;而int_least8_t保证最少有8bit,少于8bit的整数可以安全存放再int_least8_t中.例如,有个整数数组,每个元素包含16bit的值,并且被频繁使用.如果我们将其定义为int_least16_t,那么在有些平台上可能会分配64bit,从而更快的访问数组元素,提高性能.类似的,假设有下面的代码:{short int n;.for(n=0;n 500;++n)c[n]=a[n]+b[n];}循环阀值500用16bit表示足够,可以将循环计数器定义为int_fast16_t类型,编译器能识别该类型,可能将它存放在寄存器中,从而提高性能.4.6非标准字符串函数经常会有获得字符串结尾位置指针的需求,最直接的作法是char*s=.;s+=strlen(s);这种方法执行了多余的+操作,strlen其实已经遍历到结尾了.方法2:char*s=.s=strchr(s,'[message]');strchr直接返回了末尾指针位置,但是这种作法增加了strchr比较运算.方法3:非标准函数rawmemchr(const char*,char),它在memchr的基础上减少了size参数,从而减少了比较运算,提高了效率.5.Write Better Code===5.1正确编写和使用库函数假设要实现strdup函数:实现1:char*duplicate(const char*s){char*res=xmalloc(strlen(s)+1);strcpy(res,s);return res;}其中xmallc是GNU提供的failesafe的malloc实现.改进实现2:用memcpy代替strcpy char*duplicate2(const char*s) {size_t len=strlen(s)+1;char*res=xmalloc(len)+1;memcpy(res,s,len);return res;}改进实现3:直接返回memcpy的返回值.memcpy是有返回值的. char*duplicate3(const char*s){size_t len=strlen(s)+1;return memcpy(xmalloc(len),s,len);}改进实现4:编译时优化#define duplicate4(s)\(__buildtin_constant_p(s)\?duplicate_c(s,strlen(s)+1)\:duplicate3(s))char*duplicate_c(const char*s,size_t len){return(char*)memcpy(xmalloc(len),s,len);}5.2 Computed goto有些函数由于设计或者性能的原因,难以分割成若干个小函数, 导致函数有许多条件分支,从而降低执行性能.解决办法就是使用状态机.实现状态机的最简单方法就是使用switch.另外一个办法就是使用状态跳转表(goto). 示例如下:{.switch(*cp){case'l':islong=1;++cp;break;case'h':isshort=1;++cp;break;default:}switch(*cp){case'd':.break;case'h':.break;default:}}使用状态跳转表{static const void*jumps1= {['l']=&&do_l,['h']=&&do_h,['d']=&&do_d,['g']=&&do_g};static const void*jumps2= {['d']=&&do_d, ['g']=&&do_g};goto*jmps1[*cp];do_l:islong=1;++cp;goto jumps2[*cp];do_h:isshort=1;++cp;goto jumps2[*cp];do_d:.goto out;do_g:.goto out;out:}6.Profiling==对程序进行profile分两种:1)基于时间:找出最消耗时间的代码2)基于调用关系:找出函数调用次数和调用关系有些函数很小巧,可能被调用次数很多,但执行时间并不多.6.1 grof profiling gcc编译程序的时候加上-pg选项,gcc-c foo.c-o foo.o-pg link的时候加上-profile选项gcc-o foo foo.o-profile 6.2 sprof profiling不需要重新编译,只需设置环境LD_PROFILE=libc.so.6 LD_PROFILE_OUTPUT=.然后运行程序,可以检查对库的调用.特别声明:1:资料来源于互联网,版权归属原作者2:资料内容属于网络意见,与本账号立场无关3:如有侵权,请告知,立即删除。

【转载】C代码优化方案

【转载】C代码优化方案

【转载】C代码优化⽅案C代码优化⽅案1、选择合适的算法和数据结构2、使⽤尽量⼩的数据类型3、减少运算的强度 (1)查表(游戏程序员必修课) (2)求余运算 (3)平⽅运算 (4)⽤移位实现乘除法运算 (5)避免不必要的整数除法 (6)使⽤增量和减量操作符 (7)使⽤复合赋值表达式 (8)提取公共的⼦表达式4、结构体成员的布局 (1)按数据类型的长度排序 (2)把结构体填充成最长类型长度的整倍数 (3)按数据类型的长度排序本地变量 (4)把频繁使⽤的指针型参数拷贝到本地变量5、循环优化 (1)充分分解⼩的循环 (2)提取公共部分 (3)延时函数 (4)while循环和do…while循环 (6)循环展开 (6)循环嵌套 (7)Switch语句中根据发⽣频率来进⾏case排序 (8)将⼤的switch语句转为嵌套switch语句 (9)循环转置 (10)公⽤代码块 (11)提升循环的性能 (12)选择好的⽆限循环6、提⾼CPU的并⾏性 (1)使⽤并⾏代码 (2)避免没有必要的读写依赖7、循环不变计算8、函数 (1)Inline函数 (2)不定义不使⽤的返回值 (3)减少函数调⽤参数 (4)所有函数都应该有原型定义 (5)尽可能使⽤常量(const) (6)把本地函数声明为静态的(static)9、采⽤递归10、变量 (1)register变量 (2)同时声明多个变量优于单独声明变量 (3)短变量名优于长变量名,应尽量使变量名短⼀点 (4)在循环开始前声明变量11、使⽤嵌套的if结构1、选合适的算法和数据结构选择⼀种合适的数据结构很重要,如果在⼀堆随机存放的数中使⽤了⼤量的插⼊和删除指令,那使⽤链表要快得多。

数组与指针语句具有⼗分密切的关系,⼀般来说,指针⽐较灵活简洁,⽽数组则⽐较直观,容易理解。

对于⼤部分的编译器,使⽤指针⽐使⽤数组⽣成的代码更短,执⾏效率更⾼。

在许多种情况下,可以⽤指针运算代替数组索引,这样做常常能产⽣⼜快⼜短的代码。

如何把c语言转成汇编语言整理

如何把c语言转成汇编语言整理

如何把c语言转成汇编语言整理如何将C语言转为汇编语言整理汇编语言是一种非常底层的编程语言,与机器语言最为接近,通常用于编写底层软件和嵌入式系统。

C语言则是一种高级语言,更易于理解和编写,但在某些特定场景下,需要将C语言转换为汇编语言来进行优化或适配。

本文将介绍如何将C语言代码转化为汇编语言代码的步骤和技巧。

以下是整个过程的概述:1. 设置编译器参数2. 编写C语言代码3. 编译C语言代码为汇编语言代码4. 优化汇编语言代码接下来,我们将详细讨论每个步骤。

一、设置编译器参数在开始之前,我们需要确保编译器正确配置,并设置参数以生成汇编语言文件。

使用GCC编译器作为示例,以下为一些常用参数:```bashgcc -S -masm=intel -o output.asm input.c```参数说明:- -S:生成汇编语言文件而不是可执行文件- -masm=intel:使用Intel格式的汇编语言- -o output.asm:指定输出文件名- input.c:源代码文件名二、编写C语言代码在开始编写C语言代码之前,我们需要明确将代码转化为汇编语言的目的,以便选择合适的代码结构和编写方式。

以下是一些需要注意的事项:1. 了解目标平台:不同的处理器架构和操作系统可能有不同的指令集和调用约定。

在编写C语言代码时,要确保与目标平台兼容。

2. 使用合适的数据类型和算法:在某些情况下,使用特定的数据类型和算法可以提高代码效率并简化转化过程。

3. 避免复杂的控制结构:汇编语言不像高级语言那样具有丰富的控制结构,因此,在编写C语言代码时,尽量避免过于复杂的循环和递归结构。

三、编译C语言代码为汇编语言代码完成C语言代码的编写后,使用配置好的编译器参数将其转化为汇编语言代码。

在转化过程中,编译器将根据C语言代码生成相应的汇编语言指令。

生成的汇编语言文件可以使用文本编辑器打开进行查看和编辑。

四、优化汇编语言代码生成的汇编语言代码可能会存在一些冗余或者可优化的地方,可以通过手动优化或使用优化工具来提高代码效率。

嵌入式 ARM的C C++代码优化方法

嵌入式 ARM的C C++代码优化方法

ARM的C代码优化方法本文来自:我爱研发网() - R&D大本营详细出处:/Blog/Archive_Thread.asp?SID=18589=======================================================C数据类型1. C语言的程序优化与编译器和硬件系统都有关系,设置某些编译器选项是最直接最简单的优化方式。

在默认的情况下,armcc是全部优化功能有效的,而GNU编译器的默认状态下优化都是关闭的。

ARM C编译器中定义的char类型是8位无符号的,有别于一般流行的编译器默认的char是8位有符号的。

所以循环中用char变量和条件i ≥0时,就会出现死循环。

为此,可以用fsigned -char(for gcc)或者-zc(for armcc)把char改成signed。

其他的变量类型如下:char 无符号8位字节数据short 有符号16位半字节数据int 有符号32位字数据long 有符号32位字数据long long 有符号64位双字数据2. 关于局部变量大多数ARM数据处理操作都是32位的,局部变量应尽可能使用32位的数据类型(int或long)就算处理8位或者16位的数值,也应避免用char和short以求边界对齐,除非是利用char 或者short的数据一出归零特性(如255+1=0,多用于模运算)。

否则,编译器将要处理大于short和char取值范围的情况而添加代码。

另外对于表达式的处理也要格外小心,如下例子:short checksum_v3(short * data){unsigned int i;short sum = 0;for(i = 0; i < 64 ; i++){sum = (short)( sum + data );//这里表达式式整形的,所以返处理非32位数据时,//要小心处理数据类型的转换。

//原来short+short=int 但int +int=int。

关于在ARM中(MDK下)C与汇编混合编程的问题

关于在ARM中(MDK下)C与汇编混合编程的问题
{
loopLDRBr2,[r0],#1//R0保存第一个参数
STRBr2,[r1],#1//R1保存第二个参数
CMPr2,#0
BNEloop
BLXlr//返回指令须要手动加入
}
intmain(void)
{
constchar*a=“Helloworld!”;
charb[20];
my_strcpy(a,b);
关于在ARM中(MDK下)C与汇编混合编程的问题
于:bbs.21ic/icview-156494-1-1.html([微控制器/MCU]小窍门:Cortex-M3
在MDK C语言中嵌入汇编语言的方法)
==================================
==========================
如果须要访问C程式中的变量,可以使用_cpp关键字,编译器如LDRr0,=__cpp(&some_variable)
LDRr1,=__cpp(some_function)
BL__cpp(some_function)
MOVr0,#__cpp(some_constant_expr)
**********************************
***************************
在传统的ARM处理器中(ARM7/ARM9),如果要在C程式中嵌入汇编,可以有
两种方法:
一、内联汇编的方式方法如下:intPCBsheji(inti)
{
intr0;
__asm
{
ADDr0,i,1
EORi,r0,i
}
returni;
}
在汇编语句可以直接做用C语言中的变量.编译器会对这些代码进一步优化,

DSP平台c语言编程优化方法精

DSP平台c语言编程优化方法精

数又很多,往往几个时脉就可以完成却浪费时间在存取堆栈的内容上,所以干脆将这些很短的子程序直接写在主程序当中,以减少时脉数。

方法六写汇编语言虽然由C语言所编译出来的汇编语言可以正确无误的执行,但是这个汇编语言却不是最有效率的写法,所以为了增加程序的效率,于是在某些地方,例如一些被呼叫很多次且程序代码不长的函式(function),必须改以自己动手写汇编语言来取代。

方法七利用平行处理的观念C6x是一颗功能强大的处理器,它CPU勺内部提供了八个可以执行不同指令的单元,也就是说最多可以同时处理八个指令。

所以如果我们可以用它来作平行处理,我们就可以大大的缩短程序执行的时间,最有效率的来利用它来作解码的动作。

最后还要知道:第三级优化(-03),效率不高(经验),还有一些诸如用一条读32位的指令读两个相邻的16位数据等,具体情况可以看看C优化手册。

但这些效率都不高(虽然ti的宣传说能达到80%我自己做的时候发现绝对没有这个效率!65泌差不多),如果要提高效率只能用汇编来做了。

还有要看看你的c程序是怎么编的,如果里面有很多中断的话,6000可以说没什么优势。

还有,profiler 的数据也是不准确的,比实际的要大,大多少不好说。

还有dsp在初始化的时候特别慢,这些时间就不要和pc机相比了,如果要比就比核心的部分。

关于profileC6x的Debug工具提供了一个profile 界面。

在图9中,包括了几个重要的窗口,左上角的窗口是显示出我们写的C语言,可以让我们知道现在做到了哪一步。

右上角的窗口显示的是C6x所编译出来的汇编语言,同样的我们也可以知道现在做到了哪一步。

左下角的窗口是命令列,是让我们下指令以及显示讯息的窗口。

而中间的profile 窗口就是在profile模式下最重要的窗口,它显示出的项目如下表:表5:profile 的各项参数[8]字段意义Cou nt被呼叫的次数In elusive 包含子程序的总执行clock数Inel-Max包含子程序的执行一次最大clock数Exclusive不包含子程序的总执行clock数Excl-Max不包含子程序的执行一次最大clock数利用这个profile 模式我们可以用来分析程序中每个函数被呼叫的次数、执行的时脉数等等。

DSP应用程序中C代码和汇编代码的结合

DSP应用程序中C代码和汇编代码的结合

DSP应用程序中C代码和汇编代码的结合◆ CEVA公司高级编译器项目经理Eran Balaish随着DSP处理器的功能日益强大,加上编译器的优化技术不断提高,以往只利用汇编语言编写DSP应用程序的普遍做法已逐渐被淘汰。

今天,几乎所有的DSP应用程序都是由C代码和汇编代码共同构成。

对于性能是核心的关键性功能,DSP工程师继续使用高度优化的汇编代码,而其它功能则采用C语言编写,以便于维护和移植。

C代码和汇编代码的结合需要DSP工程师在工具箱中备有专用工具和方法。

众所周知,汇编代码编程的性能更好,而C代码编程的优势在于编写更为方便、快捷。

为了解释清楚原因,让我们仔细对比一下汇编代码和C代码编程的优缺点。

汇编代码编程的优势● 汇编代码能够利用处理器独有的指令和各种专用硬件资源。

另一方面,C代码则是通用性的,必须支持不同的硬件平台,因此,对C代码来说,支持专用平台代码十分困难。

● 汇编代码的编程人员通常对应用非常熟悉,并可能做出编译器难以企及的设想。

● 汇编代码编程人员可以发挥人们的创造力;而编译器再先进也只是一个自动程序而已。

汇编代码编程的缺点:● 汇编代码编程人员不得不处理耗时的机器级问题,比如寄存器分配和指令调度。

而对C代码,这些问题都可交给编译器去做。

● 汇编代码编程需要拥有关于DSP架构及其指令集的专业知识,而C编码只需要掌握广为流行的C语言即可。

● 采用汇编代码,平台之间的应用移植极其困难和耗时。

而C应用程序的移植要简单得多。

图1显示了如何利用专用硬件机制来高度优化汇编代码。

左边的C代码实现方案利用模数运算创建了循环缓冲器。

右边的是高度优化的汇编代码,利用CEVA-TeakLite-III DSP核的模数机制来创建相同的缓冲器。

只要缓冲器指针(这里是r0)更新,该模数机制就自动执行模数算法。

这种运算和指针更新发生在同一个周期内,故汇编代码比C代码有效得多,其可为模数运算产生单独的指令。

在DSP应用中选择适当的C和汇编代码混合问题在于C 代码和汇编代码之间的界限究竟在哪里,答案由分析器提供的性能分析给出。

C语言性能优化代码优化和算法改进

C语言性能优化代码优化和算法改进

C语言性能优化代码优化和算法改进C语言性能优化:代码优化和算法改进在编程领域中,性能优化是提高程序运行效率和响应速度的关键因素之一。

C语言是一种强大的编程语言,但在实际应用中,如果不进行代码优化和算法改进,可能会导致程序运行速度慢、内存占用过大等问题。

本文将介绍一些C语言性能优化的方法,包括代码优化和算法改进。

1. 代码优化代码优化是通过改进程序代码的结构和语法,以减少运行时的时间和空间开销。

以下是一些常用的代码优化技巧:1.1 减少循环次数循环是程序中常见的结构之一,在提升性能时,我们需要尽量减少循环的次数。

可以考虑使用更高效的循环方式,如使用while循环替代for循环,避免不必要的循环条件判断。

1.2 使用局部变量在编写代码时,尽量使用局部变量而不是全局变量。

局部变量的访问速度更快,可以减少内存访问时间,从而提高程序性能。

1.3 避免重复计算在某些情况下,同样的计算可能会在代码中多次出现,可以通过使用中间变量来避免重复计算,从而提高代码的执行效率。

1.4 使用位操作位操作是C语言的特有特性,可以通过位操作实现一些复杂的运算,例如位与、位或、位异或等。

合理利用位操作可以提高程序的效率。

2. 算法改进算法改进是通过优化程序中的算法,以减少运算和存储资源的使用,从而提高程序性能。

以下是一些常用的算法改进技巧:2.1 数据结构选择在选择数据结构时,需要根据具体的应用场景分析选择合适的数据结构。

例如,当需要频繁进行插入和删除操作时,可以选择链表而不是数组,以提高效率。

2.2 缓存优化利用缓存机制可以减少内存访问时间,从而提高程序运行速度。

可以通过调整数据的存储方式、使用局部性原理等方法来进行缓存优化。

2.3 分而治之对于一些复杂的问题,可以使用分而治之的思想将问题划分为多个子问题,并通过递归或迭代的方式分别解决子问题,最后将结果合并。

这种方式可以显著提高程序运行效率。

2.4 并行计算利用多线程或并行计算技术可以将程序的计算任务分配给多个处理器或核心,并发执行,从而提高程序的运行速度。

C语言中的代码优化技巧

C语言中的代码优化技巧

C语言中的代码优化技巧在C语言中,代码优化技巧是提高程序性能和效率的关键。

通过优化代码,可以在不改变程序功能的情况下,使程序运行更快、占用更少的内存空间。

下面介绍一些C语言中常用的代码优化技巧:1. 减少函数调用:函数调用是一种很消耗资源的操作,因为需要保存现场和恢复现场。

可以通过将多个函数调用合并成一个函数,减少函数嵌套的层数,避免过度的函数调用来提高程序的性能。

2. 使用适当的数据类型:在C语言中,选择适当的数据类型可以减少内存占用和提高运行速度。

比如使用整型数据类型int来代替浮点型数据类型float,可以减少内存占用和提高运行速度。

3. 避免使用过多的指针:指针操作虽然可以提高程序的效率,但是过多的指针操作会增加程序的复杂性和出错的可能性。

可以尽量减少指针的使用,使用数组或者结构体来代替指针操作。

4. 减少循环的次数:循环是程序中占用时间最多的部分,可以通过减少循环的次数或者优化循环体内部的代码来提高程序的性能。

5. 避免使用全局变量:全局变量会增加程序的耦合性和复杂度,可以尽量减少全局变量的使用,使用局部变量来代替全局变量。

6. 使用预处理器宏:使用预处理器宏可以在编译阶段进行代码替换,提高程序的性能。

可以将一些常量或者简单的函数用宏来替代,减少函数调用和代码量。

7. 减少内存拷贝操作:内存拷贝操作是一种消耗资源的操作,可以通过使用指针来避免过多的内存拷贝操作。

8. 编译器优化选项:编译器提供了很多优化选项来帮助优化代码,比如-O2、-O3等级别的优化选项可以提高程序的性能。

总之,代码优化是一个综合性的工作,需要综合考虑程序的结构、算法和编译器的优化选项。

通过合理的优化技巧,可以使程序更加高效和优化,提高程序的性能和效率。

希望以上介绍的C语言中的代码优化技巧能够帮助您优化您的程序。

mingw gcc编译汇编代码

mingw gcc编译汇编代码

mingw gcc编译汇编代码编译汇编代码是一项重要的技能,尤其对于使用mingw gcc编译器的开发者来说。

汇编代码是一种低级语言,与高级语言相比,更接近计算机硬件的指令集。

因此,通过编写汇编代码,我们可以更深入地了解计算机的工作原理,并且可以优化程序的性能。

在使用mingw gcc编译汇编代码之前,我们需要先了解一些基础知识。

首先,我们需要知道汇编语言是如何工作的。

汇编语言使用一系列的指令来告诉计算机执行特定的操作,比如加载数据到寄存器、进行算术运算、分支跳转等等。

这些指令是由特定的操作码和操作数组成的。

在编写汇编代码时,我们需要使用特定的汇编语法。

例如,在x86架构上,我们可以使用AT&T语法或Intel语法。

AT&T语法使用`movl`指令来将数据从一个地方移动到另一个地方,而Intel语法使用`mov`指令来实现相同的功能。

此外,我们还需要了解寄存器的使用和内存的访问方式。

编写汇编代码需要一定的技巧和经验。

一些常见的技巧包括使用位运算来代替乘法和除法,使用位移操作来代替乘法和除法,以及使用循环展开来提高程序的性能。

此外,我们还可以使用优化选项来告诉编译器如何生成更高效的汇编代码。

一旦我们编写好了汇编代码,就可以使用mingw gcc编译器将其转换成机器码。

编译器会将汇编代码转换成二进制形式,然后将其链接到其他目标文件中,最后生成可执行文件。

在编译过程中,我们可以使用不同的编译选项来控制编译器的行为,比如优化级别、调试信息等等。

通过使用mingw gcc编译汇编代码,我们可以更好地理解程序的底层运行机制,并且可以编写出更高效的代码。

它是学习计算机体系结构和优化程序性能的重要工具之一。

希望本文对你了解mingw gcc编译汇编代码有所帮助。

GCC编译时优化某一个或几个函数或者不优化某一个或几个函数

GCC编译时优化某一个或几个函数或者不优化某一个或几个函数

GCC编译时优化某⼀个或⼏个函数或者不优化某⼀个或⼏个函数有的时候我们会有这样的需求:不想让编译器优化某⼀个或⼏个函数、针对某⼀个或⼏个函数做设置特殊的优化等级。

以下有三种⽅法:__attribute((optimize(“STRING”)))的例⼦,fun1函数使⽤O0优化级别,fun2函数使⽤O2优化级别。

// ⾸先⽤__attribute__声明函数int fun1(int a, int b) __attribute__((optimize("O0")));// 然后再定义函数,声明和定义必须分开,否则编译错误int fun1(int a, int b){printf("fun1 is (optimize(\"O0\")");}int fun2(int a, int b) __attribute__((optimize("O2")));int fun2(int a, int b){printf("fun2 is (optimize(\"O2\")");}pragma GCC optimize (“string”…)的例⼦,pragma语句下⾯的fun4和fun5函数都使⽤O3优化级别。

pragma语句上⾯的fun3函数使⽤命令⾏指定的优化级别。

int fun3(int a, int b){printf("fun3 is optimize default");}#pragma GCC optimize ("O3")int fun4(int a, int b){printf("fun4 is (optimize(\"O3\")");}int fun5(int a, int b){printf("fun5 is (optimize(\"O3\")");}#pragma GCC push_options#pragma GCC pop_optionsThese pragmas maintain a stack of the current target and optimization options. It is intended for include files where you temporarily want to switch to using a //这些实⽤程序保留了当前⽬标和优化选项的堆栈。

c语言转汇编语言

c语言转汇编语言

c语言转汇编语言C语言作为一种高级编程语言,被广泛应用于软件开发领域。

然而,在某些特定场景下,需要对C语言进行优化或者进行底层开发时,我们可能需要将C语言代码转换成汇编语言。

本文将探讨C语言转汇编语言的方法和技巧。

一、C语言与汇编语言的关系C语言是一种结构化的高级编程语言,它提供了丰富的语法和库函数,使得软件开发更加便捷高效。

而汇编语言则是一种底层的机器语言,直接操作硬件资源,对于性能优化和底层开发具有重要意义。

C语言和汇编语言之间存在一定的对应关系,我们可以通过编译器将C语言代码转换为汇编语言代码。

二、C语言转汇编语言的方法1. 使用编译器选项大多数C语言编译器均提供了选项来生成对应的汇编语言代码。

以GCC编译器为例,我们可以使用"-S"选项来生成汇编代码。

例如:```gcc -S example.c```执行以上命令后,GCC将会生成一个名为"example.s"的汇编代码文件。

2. 内联汇编除了使用编译器选项外,我们还可以通过在C语言代码中嵌入汇编代码来实现C语言转汇编语言的目的。

这种方式称为内联汇编。

例如,下面的代码演示了如何使用内联汇编实现简单的加法操作:```int add(int a, int b) {int result;asm volatile("add %1, %2, %0": "=r"(result): "r"(a), "r"(b));return result;}```在上述代码中,"asm volatile"关键字表示内联汇编的开始,接着是汇编指令,通过":"后的输入和输出操作数来传递参数和返回结果。

三、C语言转汇编语言的优化技巧1. 减少函数调用函数调用会引入额外的开销,因此在性能要求较高的场景下,可以通过减少函数调用来提高执行效率。

Chap10(C的性能优化)

Chap10(C的性能优化)
14
与优化有关的其它编译选项
• 建议使用
– -pm – -mt – -x2
Aliasing
与-o3合用,进行程序级优化 两个指针指向同一个变量, 或一个指针修改后指向 程序中没有数据aliasing 另外一个变量 函数内联
• 不要使用
– -ml 大模式(使得.bss段内的变量都按far方式访 问) – -g 符号调试 – -s, -ss, -os C编译器生成的汇编文件内,C语句 作为注释出现
• 所有全局变量和静态变量都定义为near,其标号表示 在.bss段内的偏移地址(在其它地方,标号一般表示一 个绝对地址)-对堆栈的访问也是用这种方法实现的 (只不过基地址SP用B15表示)。
8
为什么要使用Far变量?
• 程序中使用的全局变量和静态变量超过了 32K字节 • 需要把变量存放在.bss以外的数据段
6
Near变量的生成和使用
C语言
汇编语言
相对偏移地B15(12),
Reg
7
Near变量的生成和使用
• 如果不用far特别说明,C编译器会默认地将全局和静 态变量分配在.bss段,并使用页指针B14(DP)-基 地址+offset的方法来访问。
所有全局变量和静态变量都分配在.bss段内 .bss的开始地址被称为基地址或页指针,用DP来表示, 在C6000 C编译器即B14
_n .usect .far,2,2 ldw.d1 mvk _n, A1 mvkh _n, A1 ldw.d1 *+A1, A0 在.bss内分配地址 在.far内分配地址 一条指令访问 三条指令访问
.bss _n, 4, 4 *+DP(_n), A0
5
• 了解C编译器对变量的访问方式,尤其是 全局变量和静态变量是非常重要的。这 是因为不同的方法效率不同,直接影响 到程序的执行速度。

如何优化C语言代码

如何优化C语言代码

如何优化C语言代码优化C语言代码是提高代码性能和效率的关键步骤。

以下是一些常见的优化C语言代码的方法:1.减少内存访问次数:尽量减少频繁的内存读写操作,可以使用局部变量存储重复使用的值,减少对内存的访问次数。

2.使用适当的数据结构:选择适合特定问题的数据结构,可以提高代码的效率。

例如,使用散列表来加快查找速度,使用链表来方便插入和删除操作。

3.避免不必要的循环:尽量减少循环体内的操作次数,可以通过合并循环、使用更高效的算法或数据结构来实现。

4.减少函数调用次数:函数调用会有一定的开销,尽量减少不必要的函数调用次数,可以将一些独立的操作直接内嵌到主函数中。

5.使用位运算:位运算通常比算术运算更快。

可以使用位运算替代一些常见的操作,如乘法、除法和取模运算。

6.优化循环:循环是程序中最常见的结构之一,优化循环可以显著提高程序性能。

可以使用循环展开、循环重排等技术来优化循环。

7.使用编译器优化选项:现代编译器提供了一些优化选项,可以通过使用这些选项来让编译器自动优化代码。

8.避免过度优化:过度优化可能导致代码可读性差、维护困难,且可能并不一定提高性能。

需要在性能和代码质量之间取得平衡。

9.并行化和并发:利用多线程或并行计算来加速代码的执行,可以有效提高代码的性能。

10.消除重复计算:避免重复计算可以减少不必要的开销。

可以使用变量缓存计算结果,避免重复计算相同的值。

11.内联函数:将一些简单的函数转为内联函数,可以减少函数调用开销,提高代码效率。

12.使用指针操作:指针操作通常比数组下标操作更高效。

可以使用指针进行数组遍历和元素访问。

13.减少动态内存分配:动态内存分配是一种相对开销较大的操作,尽量减少动态内存分配次数,可以使用静态分配、栈分配或内存池等方法。

14.使用高效的算法和数据结构:选择适合特定问题的高效算法和数据结构,可以大大提高代码的性能。

15.测试和评估:通过测试和评估不同的优化策略,找出最适合特定场景的优化方法。

c语言修改汇编指令

c语言修改汇编指令

c语言修改汇编指令C语言中如何修改汇编指令引言:汇编语言是一种底层的机器语言,与高级语言相比,它更加接近计算机硬件,可以直接操作寄存器和内存。

在C语言中,我们可以使用内嵌汇编来直接插入汇编指令,以实现一些特定的功能。

本文将介绍如何在C语言中修改汇编指令。

一、修改寄存器的值在C语言中,我们可以使用内嵌汇编来修改寄存器的值。

例如,下面的代码将把寄存器eax的值设置为10:```c__asm__("movl $10, %eax");```这条指令使用了movl汇编指令,将立即数10传送到eax寄存器中。

二、修改内存中的数据除了修改寄存器的值,我们还可以使用内嵌汇编来修改内存中的数据。

例如,下面的代码将把值20存储到内存地址0x1000处:```c__asm__("movl $20, 0x1000");```这里的movl指令将立即数20存储到内存地址0x1000处。

三、修改标志位在C语言中,我们可以使用内嵌汇编来修改标志位。

例如,下面的代码将把标志位CF(进位标志位)设置为1:```c__asm__("stc");```这条指令使用了stc汇编指令,将标志位CF设置为1。

四、跳转指令在C语言中,我们可以使用内嵌汇编来实现跳转指令。

例如,下面的代码将跳转到标签label处:```c__asm__("jmp label");```这条指令使用了jmp汇编指令,将程序的控制权转移到标签label 处。

五、调用函数在C语言中,我们可以使用内嵌汇编来调用汇编函数。

例如,下面的代码调用了一个名为myfunc的汇编函数:```c__asm__("call myfunc");```这条指令使用了call汇编指令,调用了myfunc函数。

六、嵌套汇编在C语言中,我们还可以使用嵌套汇编来实现更复杂的功能。

嵌套汇编可以将汇编代码嵌入到C语言代码中,实现更高级的操作。

C51优化设计之循环语句(上)——使用DJNZ循环指令提高执行效率

C51优化设计之循环语句(上)——使用DJNZ循环指令提高执行效率

C51优化设计之循环语句(上)——使用DJNZ循环指令提高执行效率[原创] 主题词:高效代码;循环语句;Keil C51;DJNZC51有三种循环语句即while,do-while和for,这三种循环都可以用来处理同一问题,基本上三者可以相互替换.但由于C51是针对51汇编语言的编译器,如果不注意51汇编指令的特点,不同的编程方式可能得到不同的程序性能(执行速度和代码长度).以计算1+2+3+...+9+10为例,下面做一对比.程序1:unsigned char i;unsigned char sum;for(i=1,sum=0;i<11;i++){sum+=i;}汇编代码为:C:0x0003 7F01 MOV R7,#0x01C:0x0005 E4 CLR AC:0x0006 FE MOV R6,AC:0x0007 EF MOV A,R7C:0x0008 2E ADD A,R6C:0x0009 FE MOV R6,AC:0x000A 0F INC R7C:0x000B BF0BF9 CJNE R7,#0x0B,C:0007代码长度(字节):11,执行周期(机器周期):63程序2:unsigned char i;unsigned char sum;for(i=10,sum=0;i;i--){sum+=i;}汇编代码为:C:0x000F 7F0A MOV R7,#0x0AC:0x0011 E4 CLR AC:0x0012 FE MOV R6,AC:0x0013 EF MOV A,R7C:0x0014 2E ADD A,R6C:0x0015 FE MOV R6,AC:0x0016 DFFB DJNZ R7,C:0013代码长度(字节):9,执行周期(机器周期):53程序3:unsigned char i=11;unsigned char sum=0;while(i--){sum+=i;}汇编代码为:C:0x0003 7F0A MOV R7,#0x0BC:0x0005 E4 CLR AC:0x0006 FE MOV R6,AC:0x0007 AD07 MOV R5,0x07C:0x0009 1F DEC R7C:0x000A ED MOV A,R5C:0x000B 6005 JZ C:0012C:0x000D EF MOV A,R7C:0x000E 2E ADD A,R6C:0x000F FE MOV R6,AC:0x0010 80F5 SJMP C:0007代码长度(字节):15,执行周期(机器周期):130从以上三个不同程序可以看出,其运算结果都是0x37(55),但最短代码为9,最长代码为15,最快速度为53,最慢速度为130,可见三个程序的性能差异较大.如何编出占用空间小运行效率高的循环代码呢?在C51编译环境下要写出优秀的循环代码必须熟悉51汇编语言的指令系统.观察程序2,循环控制指令使用了DJNZ循环转移指令,该指令同时完成计数和循环判断两种操作,而且只占用两个字节,是51指令系统中最为高效的循环指令,因此在设计循环程序时,应尽可能使C51将DJNZ用于循环程序中.当然DJNZ指令的循环次数是确定的,主要用在有确定循环次数的情况.DJNZ指令的一个最大特点是递减计数,因此循环程序必须采用递减方式才有可能编译出DJNZ指令,如以上程序2.DJNZ指令的另一个特点是先减后判断,因此设计循环程序也必须坚持先减后判断的原则,否则得不到DJNZ指令,如以上程序3.如果将程序3改写为:unsigned char i=10;unsigned char sum=0;while(i){sum+=i;i--;}就可以得到与程序2相同的汇编代码.若i--后还有其它操作,比如改为:unsigned char i=10,j=0;unsigned char sum=0;while(i){sum+=i;i--;j++;}也得不到DJNZ汇编指令,也就是说,循环语句在执行过程中,减1与判断必须是连续的,且减1在前,判断在后.对于while循环,当将减1与判断合成一步时,应当采用while(--i).按照以上所述,do-while循环同样可以汇编出DJNZ指令,不再一一列举.但是当循环变量不是通过常数赋值语句完成,而是来自于另一个变量时,for和while语句无论采用何种控制流程都不能产生DJNZ指令,因为这两种循环都是先判断后执行的控制逻辑,而DJNZ的执行过程是先执行循环体后进行循环判断.按照DJNZ的控制流程,只有do-while语句符合这个条件,因此当循环次数不是常量而是变量时,就必须使用do-while循环语句了.综上所述,若要使用DJNZ指令提高程序效率,在设计循环程序中应坚持以下三大原则:①采用递减计数;②先减后判断,减与判断连续进行;③循环次数为变量时,采用do-while循环.Keil C51垃圾代码之二——优化级别7与8难以两全导致垃圾代码[原创] 主题词:Keil C51;优化级别(优化等级);垃圾代码;OPTIMIZE注:文中提到的Keil C51版本是8.16.Keil C51编译器优化级别设置越高,编译出的代码就越精炼,执行效率也就越高.C51优化级别有两个重要特点,一是一个优化级别包含所有比它低的优化级别,二是可以对某个函数设置优化级别,但一个函数只能有一个优化级别,不允许在函数内部设置优化级别.优化级别的这两个特点决定了在函数一部分代码需要设置较高级别而另一部分需要设置较低级别的情况下,函数优化级别只能舍高取低,从而导致可能产生的垃圾代码.下面举一例子说明这一问题.设在某test函数中调用三次另一func函数,func函数为一个参数变量,无返回值,定义如下:void func(unsigned char arg){ACC = arg; // 改写累加器}调用func函数参数变量取值都为0,调用代码为:void test(void){func(0);func(0);func(0);}当将优化级别设置为8时(#pragma OPTIMIZE(8)),调用test函数的汇编代码为: 10: void test(void)11: {12: func(0);C:0x0003 E4 CLR AC:0x0004 FF MOV R7,AC:0x0005 LCALL func(C:0011)13: func(0);C:0x0008 LCALL func(C:0011)14: func(0);15: }16:C:0x000B LJMP func(C:0011)由以上汇编代码可以看出,函数参数变量只赋值一次,参数变量得到了重复利用.但必须注意的是最后一次调用func函数不是LCALL指令而是LJMP指令,这样优化可以减少一次压栈和出栈操作,多数情况下以LJMP代替LCALL/RET不会带来新的问题,但如果func函数含有针对堆栈的操作如操作系统函数,将有可能对程序执行造成灾难性后果,在这种情况下就不允许这种替换,必须降低优化级别.当优化级别降低到7时,其汇编代码变换为:10: void test(void)11: {12: func(0);C:0x0003 E4 CLR AC:0x0004 FF MOV R7,AC:0x0005 LCALL func(C:0015)13: func(0);C:0x0008 E4 CLR A ***垃圾代码C:0x0009 LCALL func(C:0015)14: func(0);C:0x000C E4 CLR A ***垃圾代码C:0x000D LCALL func(C:0015)15: }16:C:0x0010 22 RET以上代码虽然解决了替换问题,但带来了两行垃圾代码.函数func入口参数为R7,在第一次调用时已经赋值,而函数执行并不影响R7,所以以后的调用不必再次赋值,函数调用前累加器A的内容对执行无任何影响,因此清零累加器为典型的垃圾代码.Keil C51垃圾代码之一——中断服务程序ISR中PUSH/POP累加器ACC原创] 主题词:Keil C51;效率;垃圾代码;中断注:文中提到的Keil C51版本是8.16.在Keil C51手册对中断服务程序函数描述中有这样一句话:When required, the contents of ACC, B, DPH, DPL, and PSW are saved on the stack at function invocation time.话的意思是说,如果中断服务程序(以下简称ISR)执行过程中改写了CPU寄存器中的内容,那么Keil C51在中断服务程序开始处会自动添加寄存器压栈指令,言外之意是说,如果ISR没有对某个寄存器造成破坏,Keil C51不会对该寄存器保护.这么说Keil C51还是相当智能的,但事实并非如此,比如下面这个中断函数:void ISR(void) interrupt 3{P1 = 0x30;}其汇编代码为:C:0x001B 800E SJMP ISR(C:002B)......C:0x002B C0E0 PUSH ACC(0xE0)14: void ISR(void) interrupt 315: {16: P1 = 0x30;C:0x002D MOV P1(0x90),#0x3017: }18:C:0x0030 D0E0 POP ACC(0xE0)C:0x0032 32 RETI显然ISR只对P1口进行了写操作,没有改写其它任何CPU寄存器,然而Keil C51的确对累加器ACC进行了压栈/出栈保护,这保护是不必要的,是一种典型的垃圾代码.Keil C51生成这种垃圾代码的原因在于,在.MAP文件中产生了错误的寄存器使用信息.打开MAP文件,在文件的最后一部分有描述函数使用寄存器的内容:FUNCTION . . . REG MASK========================ISR. . . . . . @0xmain . . . . . @0x1e000cfb......第11位(1000 0000 0000,0x800)恰好对应累加器ACC,该位为1说明函数改写了该寄存器,所以Keil C51才又加了两行压栈/出栈指令.通过不断摸索,发现使用using关键字可以避免生成这种垃圾代码,如:void ISR(void) interrupt 3 using 0{P1 = 0x30;}汇编代码略.需要说明的是,这种垃圾代码并非总是产生,有时不存在这种异常现象.。

C语言代码优化减少代码的复杂度和冗余

C语言代码优化减少代码的复杂度和冗余

C语言代码优化减少代码的复杂度和冗余代码优化是编程中非常重要的一个步骤,通过优化可以减少代码的复杂度和冗余,提高程序的执行效率和可读性。

本文将介绍一些常见的C语言代码优化方法,帮助读者在编写程序时减少不必要的代码。

1. 使用适当的数据结构在C语言中,使用合适的数据结构可以提高程序运行效率。

例如,如果需要频繁查找和插入操作,可以选择使用哈希表或二叉搜索树来存储数据,而不是使用线性表或数组。

合理选择数据结构可以减少代码中的循环和条件判断,提高代码的可读性和执行效率。

2. 减少循环嵌套循环嵌套是造成代码复杂度增加的一个常见原因。

在编写程序时,应尽量避免过多的循环嵌套。

可以通过拆分循环、优化算法等方式来减少循环嵌套的次数。

此外,使用合适的循环控制语句如break和continue,可以简化循环逻辑,减少代码复杂度。

3. 合理使用函数和模块化编程将功能模块化可以使程序结构更加清晰,代码更易于维护和理解。

在编写程序时,尽量将类似的代码封装成函数或模块,并合理拆分代码块,减少代码冗余。

此外,可以使用函数参数和返回值来减少全局变量的使用,避免不必要的数据依赖,提高代码的可读性。

4. 使用合适的算法和数据类型在编写程序时,应选择合适的算法和数据类型来解决问题。

合适的算法可以减少代码中的复杂逻辑和冗余操作,提高程序的执行效率。

同时,合适的数据类型可以在保证功能的前提下减少代码长度,提高代码可读性。

5. 避免重复计算和冗余代码在编写程序时,应尽量避免重复计算和冗余代码。

重复计算会增加程序的运行时间,而冗余代码则增加了程序的复杂度和维护成本。

可以通过使用合适的变量存储计算结果,复用代码段等方式来避免重复计算和冗余代码。

总结:通过使用适当的数据结构、减少循环嵌套、合理使用函数和模块化编程、使用合适的算法和数据类型、避免重复计算和冗余代码等方式,可以有效减少代码的复杂度和冗余,提高代码的可读性和执行效率。

在编写C语言代码时,应养成良好的编码习惯,注重代码的优化,以提高程序的质量和性能。

vs 中c语言编译成汇编

vs 中c语言编译成汇编

vs 中c语言编译成汇编C语言是一种面向过程的编程语言,它的编译过程是将源代码转化为汇编代码,再经过汇编器转化为机器码。

在Visual Studio(简称VS)中,我们可以通过编译器将C语言代码编译成汇编代码。

本文将介绍使用VS中的C语言编译器将C语言代码转化为汇编代码的过程。

在使用VS编译C语言代码时,首先需要在VS中创建一个C语言项目。

打开VS后,选择“新建项目”,然后选择“Visual C++”下的“空项目”,并为项目命名。

接着,在新建的项目中,右键点击“源文件”,选择“添加”->“新建项”,然后选择“C++文件(.cpp)”,并为该文件命名,即可开始编写C语言代码。

在编写完C语言代码后,我们需要进行编译操作。

点击菜单栏中的“生成”->“生成解决方案”,或者按下快捷键Ctrl + Shift + B,即可开始编译源代码。

编译器会将C语言代码转化为汇编代码,并生成一个.obj格式的目标文件。

接下来,我们需要将目标文件转化为可执行文件。

点击菜单栏中的“生成”->“生成解决方案”,或者按下快捷键F5,即可生成可执行文件。

此时,VS会自动调用链接器,将目标文件与所需的库文件进行链接,生成最终的可执行文件。

在编译过程中,VS会调用系统中的汇编器来将C语言代码转化为汇编代码。

汇编代码是一种低级的程序代码,使用符号和指令来表示计算机的操作。

每条汇编指令对应着一条机器指令,它们在机器码中有着一一对应的关系。

汇编代码的语法相对简单,使用助记符来表示指令和操作数。

在汇编代码中,我们可以直接操作寄存器、内存和标志位,实现对计算机的底层控制。

通过编写汇编代码,我们可以更加精确地控制程序的执行流程和数据操作,提高程序的效率和性能。

在使用VS进行C语言编译成汇编的过程中,我们可以通过调试功能来查看生成的汇编代码。

在VS中,点击菜单栏中的“调试”->“窗口”->“汇编”,即可在调试过程中查看汇编代码。

stm32 c函数编译成汇编代码

stm32 c函数编译成汇编代码

stm32 c函数编译成汇编代码STM32是一款广泛应用于嵌入式系统开发的微控制器系列,具有丰富的外设和强大的性能。

在STM32开发中,C语言是常用的编程语言,通过编写C函数可以实现各种功能。

本文将探讨如何将STM32 C函数编译成汇编代码,并介绍一些相关的知识点。

在开始之前,我们先来了解一下什么是汇编代码。

汇编代码是一种低级别的机器语言,使用助记符来代替二进制指令,更接近于人类可读的形式。

将C函数编译成汇编代码可以帮助我们深入了解函数内部的实现细节,对于性能优化和调试排错非常有帮助。

我们需要一个编译器来将C代码转换成汇编代码。

在STM32开发中,常用的编译器有Keil MDK和IAR Embedded Workbench等。

这些编译器都提供了将C代码编译成汇编代码的选项,我们只需在编译选项中勾选相应的选项即可。

在编译过程中,编译器会将C代码转换成对应的汇编代码。

下面是一个简单的示例:```c#include <stdio.h>void delay(int count){for(int i=0; i<count; i++){// 延时一段时间}}int main(){delay(1000);return 0;}```将上述代码编译成汇编代码后,得到的结果可能如下所示:```assemblydelay PROCpush {r4, lr}mov r4, r0mov r0, #0loopcmp r0, r4add r0, #1bne looppop {r4, pc}delay ENDPmain PROCpush {lr}mov r0, #1000bl delaymov r0, #0pop {pc}main ENDP```从上面的汇编代码可以看出,C代码中的函数被转换成了对应的汇编代码。

每个C语句都被转换成了一条或多条汇编指令,这些指令按照顺序执行,最终实现了相应的功能。

在汇编代码中,我们可以看到一些常见的汇编指令,如mov、add、cmp等。

CE优化教程之自动汇编

CE优化教程之自动汇编

CE优化教程之自动汇编CE教程之自动汇编学习各种高级外挂制作技术,马上去百度搜索(魔鬼作坊),点击第一个站进入,快速成为做挂达人。

Cheat Engine最强大的地方,莫过于他的内存反汇编功能了,这给我们提供了无限的可能。

那么,什么是汇编呢?汇编程序把汇编语言翻译成机器语言的过程称为汇编。

汇编语合中用助记符(Memoni)代替操作码,用地址符号(Symbol)或标号(Label)代替地址码。

这样用符号代替机器语言的二进制码,就把机器语言变成了汇编语言。

于是汇编语言亦称为符号语言。

用汇编语言编写的程序,机器不能直接识别,要由一种程序将汇编语言翻译成机器语言,这种起翻译作用的程序叫汇编程序,汇编程序是系统软件中语言处理的系统软件。

简单来解释就是,在不考虑组译器的情况下(实际上修改游戏的话也不需要关注这个),可以理解为,我们的CPU在处理程序时所用的低级机器语言就是汇编。

很不幸的是,汇编不愧为低级语言,学习起来极其复杂,我周围连能够看懂汇编的人都寥寥无几,更不要说会用的人了。

还好我们并不需要用汇编来写程序,只需要把最常用的几个命令了解即可。

所以我的汇编水平也是非常之烂,恐怕还有无数的错误,只能勉强应付一下常用的反汇编修改了。

那么我就现学现卖一次好了^_^。

这一次,我们的目标是最近很火热的小游戏《植物大战僵尸》。

进入开始游戏,准备开始修改。

召唤出我们的CE。

改钱的步骤太简单了,就不再浪费时间。

现在我们已经找到了太阳币的地址,点右键,选择寻找写入这个地址的地址。

回游戏,点个太阳加点钱,然后回来,果然他已经找到了操作码。

双击打开额外信息对话框,这里实际上我们可以看到非常多有用的内容,比如说关于太阳币的指针是[eax+00005560]。

关于指针修改的内容之前已经讲过了,所以这里不再重复。

不过要注意的话,直接搜索eax的数值,会出来很多个地址,令人难以确定。

所以我们可以稍微往上看一行,“mov eax,[esi+04]”这一段操作码,可以看到实际上esi里的地址就是真正的eax,添加指针的时候只要输入10455E40+5560即可。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。


push ebx push esi

push edi

push ecx lea edi, [ebp+var_44]

mov ecx, 11h

mov eax, 0CCCCCCCCh

rep stosd pop ecx

mov [ebp+var_4], ecx
4. C++代码 release 版本的优化效果
#include "stdafx.h" #include<string.h> const int MAX_NAME_LEN = 20; class Function
{ public: SetAge(int iAge) { m_age = iAge; } SetName(const char* szName) { strcpy(m_szName,szName); } private: int m_age; char m_szName[MAX_NAME_LEN];
Release 版本 _main proc near push 18h call ??2@YAPAXI@Z push eax call sub_401000 add esp, 8 xor eax, eax retn _main endp
SetAge Function__SetAge proc near
其次,是辅助寄存器 ebx,edi,esi 的状态保存。作为通用寄存器,他们经常被用在一些常见的操作中,特别是在字符串、数组 等的操作中,edi、esi 通常作为存储目的、源数据的地址指针来使用。因此这里先保存这三个寄存器的值。虽然在本例中,并没 有用到 ebx 和 esi,但是还是按照惯例保存了。
C+ห้องสมุดไป่ตู้的优化汇编代码
对于一个资深程序员来说,了解我们的程序的最底层的运行机制是很重要的,特别对于 C/C++程序员来说,这点显得尤为 突出。
在很多情况下,知道其底层运行机制对我们理解更深层次的东西是非常有帮助的。比如说,如果你对这些底层的运行记住
比较熟悉,那么可能在 COM 编程中,你会更容易理解他的虚表(vtbl)技术,或者 windows 编程中经常涉及的 TRUNK 机制。 其实在不同的 C/C++编译器中,由同样的 C/C++代码编译成的汇编(机器)代码是不同的。主要讨论 Microsoft Visual C++.Net
return 0; }
函数 main
main proc near
Debug 版本
var_48= byte ptr -48h var_8= dword ptr -8 var_4= dword ptr -4
push ebp mov ebp, esp sub esp, 48h push ebx push esi push edi lea edi, [ebp+var_48] mov ecx, 12h mov eax, 0CCCCCCCCh rep stosd push 18h call operator_new add esp, 4 mov [ebp+var_8], eax mov eax, [ebp+var_8] mov [ebp+var_4], eax mov ecx, [ebp+var_4] push ecx call j_InitFun add esp, 4 xor eax, eax pop edi pop esi pop ebx add esp, 48h cmp ebp, esp call __chkesp mov esp, ebp pop ebp retn main endp
Push 22h Mov ecx,dword prt [pFun] Call Function::SetAge 第五步,退出函数是的恢复工作,受限是恢复前面提到的 3 个常用的辅助寄存器:ebx、edi、esi。三条指令完成这步操作: Pop edi Pop esi Pop ebx 最后是释放局部变量空间,恢复现场。就是让程序在跳出子函数后,不会觉得有什么被改变了。因为本文的例子是调试版 本,所以有检测是否正确实行的代码: Add esp 0C0h Cmp ebp,esp Call @ILT+3240(_RTC_CheckEsp);非调试版本不会有这样的指令 Mov esp,ebp Pop ebp 在进入函数体时,程序就将当前的堆栈指针 esp 传入 ebp 寄存器,在程序的执行过程中不改变 ebp 中的值,在退出函数体时, 再降 ebp 中的值恢复到 esp 中,通过这样的方式来实现恢复程序现场。 3. 局部变量空间分配及栈操作 在前面谈到局部变量空间分配的问题,在本例中,InitFun 函数没没有定义任何局部变量,但是也分配了 0C0h 字节的空间。 其实在所有的 Visual C++函数中,编译器都要分配 0C0h 字节的空间。如果定义了局部变量,则在 0C0h 字节的空间的基础上再 加。 堆栈操作在汇编语言中是占到了很大的比重的,C++语言从某种程度上说也是基于堆栈的语言,因为它其中的好多操作都是 基于堆栈的。特别是从面向对象的角度来看,一般在我们的整个程序中,全局变量所占的比例是很小的,其它绝大多数的变量 都是局部变量。这些局部变量的分配和释放都是通过堆栈操作来完成的。 因为在 C++语言中,程序栈是向下生长的,即在堆栈空间内,变量是从高地址向低地址方向依次分配的。所以,我们在前 面看到的局部变量内存分配是通过 sub 指令来完成的,而不是 add 指令,因此,像下面的指令: Sub esp,0C0h 为函数分配 0C0h 字节的空间,在退出函数体是,也可以通过这样的指令来释放局部变量空间: Add esp 0C0h 但是,在函数体内部,可能由于某些原因,push/pop 等堆栈指针操作可能不成对,或者其它指令改变了 esp 的值,会使得这 条指令不能恢复进入函数体时的 esp 的值。 这种情况多发生在不同 DLL 版本的访问方式上。如,Borland C++编译器编译的 DLL 提供的 int Add(int,int),你在 Visual C++ 程序调用该函数,则很可能出现这种问题,因为两者在寄存器使用约定上,栈操作方式上都不尽相同。或者不同的调用约定如: __cdecl、__stdcall、__pascal、__fastcall 之间转换不明确很有可能引发这种问题。所以,如果程序当中不应该使用 Add esp 0C0h 的方式释放内存来恢复现场,而是恢复保存在基址指针 ebp 寄存器中的值来实现。 但是这也对我们提出了一个要求,不可随意改变这些寄存器的值,而我们在向 C++代码中嵌入汇编代码是很有可能在不经 意间写出这样的代码: _asm add ebp,4 它自行修改了基址指针的值,在该函数执行结束时肯定引发访问异常。 最后我们给出例子程序的堆栈操作步骤,如下图:
push ebp Mov ebp,esp Sub esp,0C0h 我们知道,ebp 寄存器在 Visual C++中是被默认用来做基址指针的。因此,在刚进入函数执行阶段,都要对 ebp 进行相应的 操作。
第一步,先保存当前 ebp 中的值,然后将他用在本函数中。 第二步,获取当前堆栈指针,获得的堆栈指针将作为局部变量的基址指针使用。最要引起我们注意的是第三条语句,在 C++ 中,程序局部变量是在堆栈中分配的,可是并没有在每个函数中发现诸如 AllocMem 等申请内存的函数或指令。实际上,函数 中的局部变量空间的分配就是由这条指令完成的。在本例中,程序分配了 0C0h 字节的空间供该子函数使用。至于为什么要分配 多少字节,我们在后面再讨论。
编译器生成的机器代码。Visual C++不同版本的编译器生成的代码没有什么大的区别,这些在讨论后面的实现中会有详细的论述。 1. C++代码与汇编码
下面我要给出一个全局函数代码,为了简单起见,代码比较简单。
Void InitFun(Function* pFun) {
pFun->SetAge(34); PFun->SetName("brucewang"); } Function 是我们定义的一个类,这个函数的功能是接受一个 Function 类型对象的指针以对该对象进行初始化。SetAge 和 SetName 是 Function 中定义的两个函数,分别设置 Function 中定义的 age 和 name 属性。下面给出 Visual C++编译之后的汇编代 码:
var_44= byte ptr -44h
var_4= dword ptr -4
arg_0= dword ptr 8


push ebp mov ebp, esp

sub esp, 44h

push ebx push esi

push edi

push ecx

lea edi, [ebp+var_44] mov ecx, 11h
;前期工作:设置基址指针,为局部变量分配内存 push ebp Mov ebp,esp Sub esp,0C0h ;保存三个常用辅助寄存器原始信息 Push ebx Push esi Push edi ;以 0CCCCCCCCh 值初始化局部变量内存空间 Lea edi,[ebp-0C0h] Mov ecx,30h Rep stos dword ptr [edi] ;函数主体,执行函数逻辑 Push 22h Mov ecx,dword prt [pFun] Call Function::SetAge Push offset string "brucewang" Mov ecx,dword ptr[pFun] Call Function::SetName Pop edi Pop esi Pop ebx Add esp 0C0h Cmp ebp,esp Call @ILT+3240(_RTC_CheckEsp) Mov esp,ebp Pop ebp 2. 解析 C++Debug 版本汇编代码 首先,我们进入函数体,就要执行三条初始化指令:
相关文档
最新文档