C语言代码优化的一些经验及小技巧(四)

无限循环优先选用for(;;),而不是while(1)

在C语言中,最常用的无限循环语句主要有两种:while(1)for(;;)。从功能上讲, 这两种语句的效果完全一样。那么,我们究竟该选择哪一种呢?

其实,for(;;)语句运行速度要快一些。按照for的 语法规则,两个分号分开的是3个表达式。现在表达式为空,很自然地被编译成无条件的跳转(即无条件循环,不用判断条件)。如代码for(;;)Microsoft Visual Studio 2010 集成开发环境VC++的Debug模式下将生成如下汇编代码:

for(;;)
00931451 jmp main+41h (931451h)

相比之下,while语句就不一样了。按照while的语法规则,while()语句中必须有一个 表达式(这里是1 )判断条件,生成的代码用它进行条件跳转。即while语句()属于有条件循环,有条件就要判断条件是否成立,所以其相对于for(;;)语句需要多几条指令。如代码 while (1)Microsoft Visual Studio 2010集成开发环境VC++的Debug模式下将生成如下汇 编代码:

while(1)
011A1451 mov   eax,1
011A1456 test  eax,eax
011A1458 je    main+55h (11A1465h)
011A1463 jmp   main+41h (11A1451h)

根据上面的分析结果,很显然,for(;;)语句指令少,不占用寄存器,而且没有判断、 跳转指令。当然,如果从实际的编译结果来看,两者的效果常常是一样的,因为大部分编译 器都会对while (1)语句做一定的优化。但是,这还需要取决于编译器。因此,我们还是应该优先选用for(;;)语句。

没有参数的函数必须用void填充

在C语言中,void的作用主要有两个:

1、对函数返回值的限定。
2、对函数参数的限定。

看一个示例函数:

int f()
{
    return 100;
}

从表面看,函数f()没有参数,也就是说,它不允许接受参数。但事实并非如此,我们来验证一下:

#include <stdio.h>
int f()
{
    return 100;
}

int main(void)
{
    printf("%d\n", f(666));
    return 0;
}

编译、运行结果为:

可见,使用GCC可正常通过编译,这说明可以向无参数的函数传递参数。但是,需要注意的是,在一些IDE中不能通过编译。

所以,为了提高程序的统一性、安全性与可读性。我们对没有参数的函数必须使用void进行填充。我们使用void填充上面的f函数之后,编译就不通过了,报错如下:

尽可能为简单功能编写函数

有时候,我们需要用函数去封装仅用一两行代码就可完成的功能。对于这样的函数,单 从代码最上看,好像没有什么封装的必要。但是,用函数可使其功能明确化、具体化,从而 增加程序可读性,并且也方便代码的维护与测试。示例代码如下:

int Max(int x,int y)
{
    return (x>y? x : y)}
int Min(int x,int y)
{
    return (x<y?x:y);
}

当然,也可以使用宏来代替上面的函数,代码如下:

#define MAX(x,y)  (((x) > (y)) ? (x) : (y))
#define MIN(x,y)  (((x) < (y)) ? (x) : (y))

在C程序中,我们可以适当地用宏代码来提高执行效率。宏代码本身不是函数,但使用起来与函数相似。预处理器用复制宏代码的方式代替函数调用,省去了参数压栈、生成汇编语言的CALL调用、返回参数、执行return等过程,从而提高了运行速度。但是,使用宏代码最大的缺点就是容易出错,预处理器在复制宏代码时常常产生意想不到的边际效应。因此, 尽管看起来宏要比函数简单得多,但还是建议使用函数的形式来封装这些简单功能的代码。

函数地抽象级别应在同一个层次

先来看下面一段示例代码:

void Init(void)
{
    /* 本地初始化 */
    ......
    /* 远程初始化 */
    InitRemote();
}

void InitRemote(void)
{
    /* 远程初始化 */
    ......
}

上面地Init函数主要完成本地初始化与远程初始化工作,在其功能上没有什么不妥之处。但从设计观点看,却存在这一定缺陷。因为本地初始化与远程初始化层次相当,本地初始化也应当作为独立的函数存在。应改为:

void Init(void)
{
    /* 本地初始化 */
    InitLocal();
    /* 远程初始化 */
    InitRemote();
}

void InitLocal(void)
{
    /* 本地初始化 */
    ......
}

void InitRemote(void)
{
    /* 远程初始化 */
    ......
}

尽量避免在非调度函数中使用控制参数

在函数设计中,我们可以将函数简单地分为两大类:调度函数与非调度函数(非调度函数一般也称为功能函数或实现函数)。

所谓的调度函数是指根据输入的消息类型或控制命令来启动相应的功能实体(即函数或过程)的函数。调度函数本身不能提供功能实现,相反,它必须委托给实现函数来完成具体的功能。也就是说,调度型函数永远只关注“what to do”,而“how to do”则是由实现函数来关心的,调度函数不需要关心“how to do”。这种调度函数与实现函数的分离设计也满足了单一职责的原则,即调度的不实现,实现的不调度。

对调度函数来讲,控制参数是指改变函数功能行为的参数,即函数要根据此参数来决定具体怎样工作。然而,如果在非调度函数中也使用控制参数来决定具体怎样工作,那么这样做无疑会增加函数间的控制耦合,很可能使函数间的耦合度增大,并使函数的功能不唯一, 违背了函数功能的单一原则。示例代码如下:

int Calculate( int a, int b, const int calculate_flag )
{
    int sum = 0;
    switch(calculate_flag)
    {
        case 1: 
            sum = a + b; 
            break;
        case 2:
            sum = a - b;
        case 3: 
            sum = a * b; 
            break;
        case 4: 
            sum = a / b; 
            break;
        defaultprintf("error\n");
            break;
    }
    return sum;
}

上面的函数虽然看起来很简洁,实际上这种设计是不合理的。由于控制参数calculate_flag的原因,使函数间的耦合度增大,也违背了函数的功能单一原则。因此,不如分为如下4个函数清晰,示例代码如下:

int Add(int a,int b)
{
    return a + b;
}
int Sub(int a,int b)
{
    return a - b;
}
int Mul(int a,int b)
{
    return a * b;
}
int Div(int a,int b)
{
    return a / b;
}

以上就是本次的分享,如有错误,欢迎指出!


我的个人博客:https://zhengnianli.github.io/

我的微信公众号:嵌入式大杂烩

我的CSDN博客:https://blog.csdn.net/zhengnianli


 上一篇
【RT-Thread笔记】内核对象模型 【RT-Thread笔记】内核对象模型
RT-Thread中的对象有哪些?RT-Thread包括了很多不同类型的对象,如线程,信号量,互斥量等。在代码中,这些对象被汇总到一个枚举中(在rtdef.h中): enum rt_object_class_type { RT_Ob
2019-09-01
下一篇 
【RT-Thread笔记】内核基础 【RT-Thread笔记】内核基础
最近在工作中有用到多任务系统,趁热进行学习一下。这里我选择国产实时操作系统RT-Thread进行学习,因为现在很火呀。之前已经有简单地过了一遍RT-Thread,奈何学过地知识一旦不用,就会很容易地忘掉,所以应当多做一些学习笔记~ RT-T
2019-08-30
  目录