2024年物联网嵌入式最新数十种嵌入式 C 语言代码优化的经验和方法_嵌入式代码优化方案,2024年最新最新整理《物联网嵌入式开发架构师面试题解析大全》

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

指针链经常被用于访问结构数据。例如,常用的代码如下:

typedef struct { int x, y, z; } Point3;
typedef struct { Point3 *pos, *direction; } Object;
 
void InitPos1(Object *p)
{
   p->pos->x = 0;
   p->pos->y = 0;
   p->pos->z = 0;
}

然而,这种的代码在每次操作时必须重复调用p->pos,因为编译器不知道p->pos->x与p->pos是相同的。一种更好的方法是缓存p->pos到一个局部变量:

void InitPos2(Object *p)
{
   Point3 *pos = p->pos;
   pos->x = 0;
   pos->y = 0;
   pos->z = 0;
}

另一种方法是在Object结构中直接包含Point3类型的数据,这能完全消除对Point3使用指针操作。

条件执行

条件执行语句大多在if语句中使用,也在使用关系运算符(<,==,>等)或者布尔值表达式(&&,!等)计算复杂表达式时使用。对于包含函数调用的代码片段,由于函数返回值会被销毁,因此条件执行是无效的。

因此,保持if和else语句尽可能简单是十分有益处的,因为这样编译器可以集中处理它们。关系表达式应该写在一起。

下面的例子展示编译器如何使用条件执行:

int g(int a, int b, int c, int d)
{
   if (a > 0 && b > 0 && c < 0 && d < 0)
   //  grouped conditions tied up together//
      return a + b + c + d;
   return -1;
}

由于条件被聚集到一起,编译器能够将他们集中处理。

布尔表达式和范围检查

一个常用的布尔表达式是用于判断变量是否位于某个范围内,例如,检查一个图形坐标是否位于一个窗口内:

bool PointInRectangelArea (Point p, Rectangle *r)
{
   return (p.x >= r->xmin && p.x < r->xmax &&
                      p.y >= r->ymin && p.y < r->ymax);
}

这里有一种更快的方法:x>min && x<max可以转换为(unsigned)(x-min)<(max-min)。这对于min等于0时更为有益。优化后的代码如下:

bool PointInRectangelArea (Point p, Rectangle *r)
{
    return ((unsigned) (p.x - r->xmin) < r->xmax &&
   (unsigned) (p.y - r->ymin) < r->ymax);
 
}

布尔表达式和零值比较

处理器的标志位在比较指令操作后被设置。标志位同样可以被诸如MOV、ADD、AND、MUL等基本算术和裸机指令改写。如果数据指令设置了标志位,N和Z标志位也将与结果与0比较一样进行设置。N标志表示结果是否是负值,Z标志表示结果是否是0。

C语言中,处理器中的N和Z标志位与下面的指令联系在一起:有符号关系运算x<0,x>=0,x0,x!=0;无符号关系运算x0,x!=0(或者x>0)。

C代码中每次关系运算符的调用,编译器都会发出一个比较指令。如果操作符是上面提到的,编译器便会优化掉比较指令。例如:

int aFunction(int x, int y)
{
   if (x + y < 0)
      return 1;
  else
     return 0;
}

尽可能的使用上面的判断方式,这可以在关键循环中减少比较指令的调用,进而减少代码体积并提高代码性能。C语言没有借位和溢出位的概念,因此,如果不借助汇编,不可能直接使用借位标志C和溢出位标志V。但编译器支持借位(无符号溢出),例如:

int sum(int x, int y)
{
   int res;
   res = x + y;
   if ((unsigned) res < (unsigned) x) // carry set?  //
     res++;
   return res;
}

懒检测开发

在if(a>10 && b=4)这样的语句中,确保AND表达式的第一部分最可能较快的给出结果(或者最早、最快计算),这样第二部分便有可能不需要执行。

用switch()函数替代if…else…

对于涉及if…else…else…这样的多条件判断,例如:

if( val == 1)
    dostuff1();
else if (val == 2)
    dostuff2();
else if (val == 3)
    dostuff3();

使用switch可能更快:

switch( val )
{
    case 1: dostuff1(); break;

    case 2: dostuff2(); break;

    case 3: dostuff3(); break;
}

在if()语句中,如果最后一条语句命中,之前的条件都需要被测试执行一次。Switch允许我们不做额外的测试。如果必须使用if…else…语句,将最可能执行的放在最前面。

二分中断

使用二分方式中断代码而不是让代码堆成一列,不要像下面这样做:

if(a==1) {
} else if(a==2) {
} else if(a==3) {
} else if(a==4) {
} else if(a==5) {
} else if(a==6) {
} else if(a==7) {
} else if(a==8)

{
}

使用下面的二分方式替代它,如下:

if(a<=4) {
    if(a==1)     {
    }  else if(a==2)  {
    }  else if(a==3)  {
    }  else if(a==4)   {

    }
}
else
{
    if(a==5)  {
    } else if(a==6)   {
    } else if(a==7)  {
    } else if(a==8)  {
    }
}

或者如下:

if(a<=4)
{
    if(a<=2)
    {
        if(a==1)
        {
            /* a is 1 */
        }
        else
        {
            /* a must be 2 */
        }
    }
    else
    {
        if(a==3)
        {
            /* a is 3 */
        }
        else
        {
            /* a must be 4 */
        }
    }
}
else
{
    if(a<=6)
    {
        if(a==5)
        {
            /* a is 5 */
        }
        else
        {
            /* a must be 6 */
        }
    }
    else
    {
        if(a==7)
        {
            /* a is 7 */
        }
        else
        {
            /* a must be 8 */
        }
    }
}

比较如下两种case语句:

======001

switch语句vs查找表

Switch的应用场景如下:

  • 调用一到多个函数
  • 设置变量值或者返回一个值
  • 执行一到多个代码片段

如果case标签很多,在switch的前两个使用场景中,使用查找表可以更高效的完成。例如下面的两种转换字符串的方式:

char * Condition_String1(int condition) {
  switch(condition) {
     case 0: return "EQ";
     case 1: return "NE";
     case 2: return "CS";
     case 3: return "CC";
     case 4: return "MI";
     case 5: return "PL";
     case 6: return "VS";
     case 7: return "VC";
     case 8: return "HI";
     case 9: return "LS";
     case 10: return "GE";
     case 11: return "LT";
     case 12: return "GT";
     case 13: return "LE";
     case 14: return "";
     default: return 0;
  }
}
 
char * Condition_String2(int condition) {
   if ((unsigned) condition >= 15) return 0;
      return
      "EQ\0NE\0CS\0CC\0MI\0PL\0VS\0VC\0HI\0LS\0GE\0LT\0GT\0LE\0\0" +
       3 * condition;
}

第一个程序需要240 bytes,而第二个仅仅需要72 bytes。

循环

循环是大多数程序中的常用的结构;程序执行的大部分时间发生在循环中,因此十分值得在循环执行时间上下一番功夫。

循环终止

如果不加注意,循环终止条件的编写会导致额外的负担。我们应该使用计数到零的循环和简单的循环终止条件。简单的终止条件消耗更少的时间。看下面计算n!的两个程序。第一个实现使用递增的循环,第二个实现使用递减循环。

int fact1_func (int n)
{
    int i, fact = 1;
    for (i = 1; i <= n; i++)
      fact *= i;
    return (fact);
}
 
int fact2_func(int n)
{
    int i, fact = 1;
    for (i = n; i != 0; i--)
       fact *= i;
    return (fact);
}

第二个程序的fact2_func执行效率高于第一个。

更快的for()循环

这是一个简单而高效的概念。通常,我们编写for循环代码如下:

for( i=0;  i<10;  i++){ ... }

i从0循环到9。如果我们不介意循环计数的顺序,我们可以这样写:

for( i=10; i--; ) { ... }

这样快的原因是因为它能更快的处理i的值–测试条件是:i是非零的吗?如果这样,递减i的值。对于上面的代码,处理器需要计算“计算i减去10,其值非负吗?如果非负,i递增并继续”。

简单的循环却有很大的不同。这样,i从9递减到0,这样的循环执行速度更快。

这里的语法有点奇怪,但确实合法的。循环中的第三条语句是可选的(无限循环可以写为for(;😉)。如下代码拥有同样的效果:

for(i=10; i; i--){}

或者更进一步的:

for(i=10; i!=0; i--){}

这里我们需要记住的是循环必须终止于0(因此,如果在50到80之间循环,这不会起作用),并且循环计数器是递减的。使用递增循环计数器的代码不享有这种优化。

合并循环

如果一个循环能解决问题坚决不用二个。但如果你需要在循环中做很多工作,这坑你并不适合处理器的指令缓存。这种情况下,两个分开的循环可能会比单个循环执行的更快。下面是一个例子:

======002

函数循环

调用函数时总是会有一定的性能消耗。不仅程序指针需要改变,而且使用的变量需要压栈并分配新变量。为提升程序的性能,在函数这点上有很多可以优化的。在保持程序代码可读性的同时也需要代码的大小是可控的。

如果在循环中一个函数经常被调用,那么就将循环纳入到函数中,这样可以减少重复的函数调用。代码如下:

for(i=0 ; i<100 ; i++)
{
    func(t,i);
}
-
-
-
void func(int w,d)
{
    lots of stuff.
}

应改为:

func(t);
-
-
-
void func(w)
{
    for(i=0 ; i<100 ; i++)
    {
        //lots of stuff.
    }
}

循环展开

简单的循环可以展开以获取更好的性能,但需要付出代码体积增加的代价。循环展开后,循环计数应该越来越小从而执行更少的代码分支。如果循环迭代次数只有几次,那么可以完全展开循环,以便消除循坏带来的负担。

这会带来很大的不同。循环展开可以带非常可观的节省性能,原因是代码不用每次循环需要检查和增加i的值。例如:

======003

编译器通常会像上面那样展开简单的,迭代次数固定的循环。但是像下面的代码:

for(i=0;i< limit;i++) { ... }

下面的代码(Example 1)明显比使用循环的方式写的更长,但却更有效率。block-sie的值设置为8仅仅适用于测试的目的,只要我们重复执行“loop-contents”相同的次数,都会有很好的效果。

在这个例子中,循环条件每8次迭代才会被检查,而不是每次都进行检查。由于不知道迭代的次数,一般不会被展开。因此,尽可能的展开循环可以让我们获得更好的执行速度。

//Example 1
 
#include<STDIO.H>
 
#define BLOCKSIZE (8)
 
void main(void)
{
int i = 0;
int limit = 33;  /* could be anything */
int blocklimit;
 
/* The limit may not be divisible by BLOCKSIZE,
 * go as near as we can first, then tidy up.
 */
blocklimit = (limit / BLOCKSIZE) * BLOCKSIZE;
 
/* unroll the loop in blocks of 8 */
while( i < blocklimit )
{
    printf("process(%d)\n", i);
    printf("process(%d)\n", i+1);
    printf("process(%d)\n", i+2);
    printf("process(%d)\n", i+3);
    printf("process(%d)\n", i+4);
    printf("process(%d)\n", i+5);
    printf("process(%d)\n", i+6);
    printf("process(%d)\n", i+7);
 
    /* update the counter */
    i += 8;
 
}
 
/*
 * There may be some left to do.
 * This could be done as a simple for() loop,
 * but a switch is faster (and more interesting)
 */
 
if( i < limit )
{
    /* Jump into the case at the place that will allow
     * us to finish off the appropriate number of items.
     */
 
    switch( limit - i )
    {
        case 7 : printf("process(%d)\n", i); i++;
        case 6 : printf("process(%d)\n", i); i++;
        case 5 : printf("process(%d)\n", i); i++;
        case 4 : printf("process(%d)\n", i); i++;
        case 3 : printf("process(%d)\n", i); i++;
        case 2 : printf("process(%d)\n", i); i++;
        case 1 : printf("process(%d)\n", i);
    }
}
 
}

统计非零位的数量

通过不断的左移,提取并统计最低位,示例程序1高效的检查一个数组中有几个非零位。示例程序2被循环展开四次,然后通过将四次移位合并成一次来优化代码。经常展开循环,可以提供很多优化的机会。

//Example - 1

int countbit1(uint n)
{
  int bits = 0;
  while (n != 0)
  {
    if (n & 1) bits++;
    n >>= 1;
   }
  return bits;
}

//Example - 2

int countbit2(uint n)
{
   int bits = 0;
   while (n != 0)
   {
      if (n & 1) bits++;
      if (n & 2) bits++;
      if (n & 4) bits++;
      if (n & 8) bits++;
      n >>= 4;
   }
   return bits;
}

尽早的断开循环

通常,循环并不需要全部都执行。例如,如果我们在从数组中查找一个特殊的值,一经找到,我们应该尽可能早的断开循环。例如:如下循环从10000个整数中查找是否存在-99。

found = FALSE;
for(i=0;i<10000;i++)
{
    if( list[i] == -99 )
    {
        found = TRUE;
    }
}
 
if( found ) 
    printf("Yes, there is a -99. Hooray!\n");

上面的代码可以正常工作,但是需要循环全部执行完毕,而不论是否我们已经查找到。更好的方法是一旦找到我们查找的数字就终止继续查询。

found = FALSE;
for(i=0; i<10000; i++)
{
    if( list[i] == -99 )
    {
        found = TRUE;
        break;
    }
}
if( found ) 
    printf("Yes, there is a -99. Hooray!\n");

假如待查数据位于第23个位置上,程序便会执行23次,从而节省9977次循环。

函数设计

设计小而简单的函数是个很好的习惯。这允许寄存器可以执行一些诸如寄存器变量申请的优化,是非常高效的。

函数调用的性能消耗

函数调用对于处理器的性能消耗是很小的,只占有函数执行工作中性能消耗的一小部分。参数传入函数变量寄存器中有一定的限制。这些参数必须是整型兼容的(char,shorts,ints和floats都占用一个字)或者小于四个字大小(包括占用2个字的doubles和long longs)。

如果参数限制个数为4,那么第五个和之后的字就会存储在栈上。这便在调用函数是需要从栈上加载参数从而增加存储和读取的消耗。

看下面的代码:

int f1(int a, int b, int c, int d) {
   return a + b + c + d;
}
 
int g1(void) {
   return f1(1, 2, 3, 4);
}
 
int f2(int a, int b, int c, int d, int e, int f) {
  return a + b + c + d + e + f;
}
 
ing g2(void) {


![img](https://img-blog.csdnimg.cn/img_convert/4e257f973290852286fab59d698847b4.png)
![img](https://img-blog.csdnimg.cn/img_convert/5a51254fbf0054078b62291a72a62693.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618679757)**

是整型兼容的(char,shorts,ints和floats都占用一个字)或者小于四个字大小(包括占用2个字的doubles和long longs)。


如果参数限制个数为4,那么第五个和之后的字就会存储在栈上。这便在调用函数是需要从栈上加载参数从而增加存储和读取的消耗。


看下面的代码:



int f1(int a, int b, int c, int d) {
return a + b + c + d;
}

int g1(void) {
return f1(1, 2, 3, 4);
}

int f2(int a, int b, int c, int d, int e, int f) {
return a + b + c + d + e + f;
}

ing g2(void) {

[外链图片转存中…(img-DIhzP5la-1715664601852)]
[外链图片转存中…(img-G7gy9Rag-1715664601853)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

  • 8
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值