北京理工大学 马忠梅
二、 C语言技巧
这部分内容包括公用表达式处理、指针的使用、循环的使用以及函数返回地址的控制。
1 公用表达式处理
(1) 消除公用表达式
在编写程序完成某些计算的过程中,有时会把同样的运算结果赋值给不止1个变量,
然后在后续的计算中使用。随着程序变大和复杂性的增加,最后可能一次次进行相
同的运算而并不知道。下面是1个把变量i,j,k运算结果赋给变量a,b,c的函数。包
含此函数的程序以列表文件的形式给出,这样可以看到程序的行号、相应行所对应
的汇编程序以及存储器的使用情况。
程序1:
1 void func1(int i,int j)
2 {
3 1 int k=20;
4 1 int a,b,c;
5 1 a=i*j/k;
6 1 b=i*j/k;
7 1 c=i*j/k;
8 1 };
FUNCTIONfunc1 (BEGIN)
0000 8E00RMOV i,R6
0002 8F00RMOV i+01H,R7;
;......|Variable 'j ' assigned to Register 'R2/R3'......
0004 AB05 MOV R3,AR5
0006 AA04 MOV R2,AR4;
SOURCE LINE # 1
; SOURCE LINE # 2
; SOURCE LINE # 3
0008 750000 R MOV k,#00H
000B 750014 R MOV k+01H,#014H;
;SOURCE LINE # 5
000E 120000 E LCALL ? C_IMUL
0011 AC00 R MOV R4,k
0013 AD00 R MOV R5,k+01H
0015 120000 E LCALL ? C_SIDIV
0018 8E00 R MOV a,R6
001A 8F00 R MOV a+01H,R7
;SOURCE LINE # 6
001C 8E00 R MOV b,R6
001E 8F00 R MOV b+01H,R7
;SOURCE LINE # 7
0020 8E00 R MOV c,R6
0022 8F00 R MOV c+01H,R7
;SOURCE LINE # 8
0024 22 RET
; FUNCTION func1 (END)
MODULE INFORMATION:STATIC OVERLAYABLE
CODE SIZE= 37 ---
CONSTANT SIZE=--- ---
XDATA SIZE=--- ---
PDATA SIZE=--- 10
DATA SIZE=--- ---
IDATA SIZE=--- ---
BIT SIZE=--- ---
END OF MODULE INFORMATION.
程序2:
1 void func2(int i,int j)
2 {
3 int k=20;
4 int a,b,c;
5 a=b=c=i*j/k;
6 }
;FUNCTION func2(BEGIN)
;--- Variable 'i ' assigned to Register 'R6/R7' ---
;--- Variable 'j ' assigned to Register 'R4/R5' ----
;SOURCE LINE # 1
;SOURCE LINE # 2
;SOURCE LINE # 3
0000 750000 R MOV k,#00H
0003 750014 R MOV k+01H,#014H
;SOURCE LINE # 5
0006 120000 E LCALL ? C_IMUL
0009 AC00 R MOV R4,k
000B AD00 RMOV R5,k+01H
000D 120000 E LCALL ?CSIDIV
0010 8E00 R MOV c,R6
0012 8F00 R MOV c+01H,R7
0014 8E00 R MOV b,R6
0016 8F00 R MOV b+01H,R7
0018 8E00 R MOV a,R6
001A 8F00 R MOV a+01H,R7
;SOURCE LINE # 6
001C 22 RET
; FUNCTION_func2 (END)
MODULE INFORMATION:STATIC OVERLAYABLE
CODE SIZE= 29 ----
CONSTANT SIZE=--- -----
XDATA SIZE=---- ----
PDATA SIZE=--- -----
DATA SIZE=---- 8
IDATA SIZE=---- ----
BIT SIZE=--- -----
END OF MODULE INFORMATION.
程序1的"func1()"有多个赋值表达式,每次(i*j/k)重新计算并把结果赋
给1个变量。在这个函数中,(i*j/k)计算3次,每次产生相同的结果。这样,
非常浪费处理时间。
程序2的"func2()"把多个赋值表达式合并成1个,完成的处理过程如下:首
先,计算(i*j/k);然后,把这个结果赋给变量c,这个结果也赋给变量b和a。
用这种方法,(i*j/k)只计算一次。
值得注重的是:程序1产生的代码是经过优化处理的。好的编译器可以完成公
用表达式的自动消除。这通过使用优化选项(OPTIMIZE)实现。Franklin
C51可支持5级代码优化。OPTIMIZE也可以控制对某一函数的代码优化,如下:
#pragma OPTIMIZE(4)
func( )
{
…
}
# pragma OPTIMIZE(5)
这样就可以在源程序中控制函数的优化等级。程序1的代码即使是优化生成的,
它也比程序2产生的代码略长。因而,在编写程序过程中要注重消除公用表达式,
以减轻编译程序的负担,提高产生代码的效率。
"消除公用表达式"是减少处理时间的1种方法,它也可以看作是以"减少表达式的
计算次数"来减少处理时间。上面只是1个特例。实际的程序可能是要有相同的计
算,但不是连续出现。这种情况下,应该把相同表达式的处理结果赋给1个变量,
然后,在以后的使用过程中访问这个变量,程序如下:
程序1:
1 void func1(int i,int j)
2 {
3 1 Int k=20;
4 1 int a,b,c;
5 1 a=(i>>4)+j-k;
6 1 b=(i>>4)+j+k;
7 1 c=((i>>4)+j)*k;
8 1}
程序2:
1 void func2(int i,int j)
2 {
3 1 int k=20;
4 1 int a,b,c,d;
5 1 a=(d=(i>>4)+j)-k;
6 1 b=d+k;
7 1 c=d*k;
8 1 }
9 在程序1中,((i>>4)+j)这个表达式被计算多次;而程序2中,((i>>4)+j)的计算
值在第1次出现时就赋给了变量d。以后再碰到相同的计算时,直接使用变量d。
(2) 简化循环
这部分讨论对循环中连续操作的处理以减少循环的运行时间。
在C语言中,"for","while"和"do-while"循环用于重复处理。循环可用于访问1个
变量,完成获得相同结果的计算或进行循环条件的判决。
下面是通过减少不变的处理来减少循环运行时间的方法。
程序1:
1 #define uchar unsigned char
2 #define uint unsigned int
3 #define LEN
4
5 void fun1(uint a,uint b)
6 {
7 1 uchar i;
8 1 uint mem[LEN];
9 1
10 1 for(i=0;i<LEN;i++)
11 1 mem[i]=a/b+10;
12 1 }
MODULE INFORMATION:STATIC OVERLAYABLE
CODE SIZE= 49 ----
CONSTANT SIZE=---- ----
XDATA SIZE=---- ----
PDATA SIZE=---- ----
DATA SIZE=---- 44
IDATA SIZE=---- ----
BIT SIZE=---- ----
END OF MODULE INFORMATION.
程序2:
1 #define uchar unsigned char
2 #define uint unsigned int
3 #define LEN
4
5 void fun2(uint a,uint b)
6 {
7 1 uchar i;
8 1 uint temp;
9 1 uint mem[LEN] ;
10 1
11 1 temp=a/b+10;
12 1 for(i=0;i<LEN;i++)
13 1
mem[i]=temp;
14 1 }
MODULE INFORMATION:STATIC OVERLAYABLE
CODE SIZE= 33 ----
CONSTANT SIZE=---- ----
XDATA SIZE=---- ----
PDATA SIZE=---- ----
DATA SIZE=---- 40
IDATA SIZE=---- ----
BIT SIZE=---- ----
END OF MODULE INFORMATION.
程序1的"func1()"在"for"循环中包括1个不变值的赋值表达式"mem[i]=a/b+10"。
由于在"for"循环中变量a和b没有变化,(a/b+10)的计算结果是1个不变值。这个
值没有必要在循环中每次计算1遍。
程序2的"func2()"的不变值表达式已被移到循环的外面。(a/b+10)在循环外计
算后赋给变量"temp"。在"for"循环中使用"temp"变量把值赋给数组元素。这样,
(a/b+10)在func1()中每次循环计算1次,而在func2()中只计算1次。
下面的例子是在"for"循环条件判决时带有1个不变的值。
1 #define uchar unsigned char
2 #define uint unsigned ini
3 #define LEN 20
4
5 void fun1(int num)
6 {
7 1 uchar I;
8 1 uint mem[LEN]
9 1
10 1 for (I=0;I<num*4+2;j++)
11 1 mem[i]=0;
12 1 }
MODULE INFORMATION; STATIC OVERLAYABLE
CODE SIZE= 51 ----
CONSTANT SIZE=--- -----
XDATA SIZE=---- ----
PDATA SIZE=---- ----
DATA SIZE=---- 42
IDATA SIZE=---- ----
BIT SIZE=----- ---
END OF MODULE INFORMATION.
程序2:
1 #define uchar unsigned char
2 #define uint unsigned int
3 #define LEN
4
5 void fun2(int num)
6 {
7 1 Uchar i,a;
8 1 uintmem[LEN];
9 1 for(i=0,a=num*4+2;i<a;i++)
10 1 mem[i]=0;
11 1 }
MODULE INFORMATION:STATIC OVERLAYABLE
CODE SIZE= 29 ----
CONSTANT SIZE=---- ----
XDATA SIZE=----- ---
PDATA SIZE=---- ----
DATA SIZE=---- 40
IDATA SIZE=----- ---
BIT SIZE=----- ---
END OF MODULE INFORMATION.
程序1中的"func1()",循环条件判决基于(num*4+2)的计算结果。由于在
"for"循环中没有num变量的处理,(num*4+2)是个不变值。对每个循环条件
判决,(num*4+2)的值都被计算1次并和变量i进行比较。(num*4+2)计算
的次数和循环的次数一样多。
程序2中的"func2()",用于循环条件判决的不变表达式从循环中移出来。
"func2()"中,(num*4+2)在初始化处理时把结果赋给了变量a,变量a
再用于循环的判决。"func2()"由于把(num*4+2)的值赋给变量a,节省
了运行时间。
建议大家把循环中的不变值计算移到循环外完成。
2 指针的使用
指针是其值为1个地址的变量。图1是指针的定义和使用。
在这个例子里,定义了"int"类型指针"*intp"和"int"类型变量"idata",然后,
用指针变量改变idata变量的值,过程如下:
· 数值1赋给idata变量,现idata值为1;
· idata变量的地址赋给指针intp;
· 数值2赋给*intp,现idata值为2。
在C语言中,指针可直接用于指定地址。这样就答应存储器中的1个值可被直接访
问或更新。
(1) 指针变量和数组
这一部分解释如何使用指针引用数组元素。数组是同种类型的数据项的收集,数
组保存在留给变量用的连续存储空间中,数组元素0在顶部(最低的)地址。
char Array[10];
char *p;
p=Array;/*指针指向数组Array[0] */
*p=1;/* *p=1使Array[0]为1*/
*(p+1)=1;/**(p+1)=1 使Array[1]为1*/
*(p+2)=1; /**(p+2)=1 使Array[2]为1*/
图2显示如何用1个指针访问定义为"Array[ ]"数组的元素。表明数值1赋给了数组
元素Array[0],Array[1]和Array[2]的过程:
· 定义字符类型数组"Array[ ]"和字符类型指针变量"p";
· 把数组"Array[ ]"的起始地址赋给指针变量"p";
· 赋值1给*p;
· 赋值1给*(p+1);
· 赋值1给*(p+2)。
下面程序比较两个函数:1个使用数组的下标访问数组元素,而另1个使用指针访问
数组元素。
程序1:
1 #define uchar unsigned char
2 #define uint unsigned int
3 #define LEN
4 uchar array1[LEN];
5 uchar array2[LEN];
6
7 void func1()
8
9 1 { uchar I;
10 1 uint sum=0;
11
12 1 for(i=0;i<LEN;i++)
13 1 sum=sum+array1[i]+array2[i];
14 1 }
MODULE INFORMATION:STATIC OVERLAYABLE
CODE SIZE= 44 ----
CONSTANT SIZE=---- ----
XDATA SIZE=---- ----
PDATA SIZE=---- ----
DATA SIZE=20 2
IDATA SIZE=---- ----
BIT SIZE=---- ----
END OF MODULE INFORMATION.
程序2:
1 #define uchar unsigned char
2 #define uint unsigned int
3 #define LEN 10
4 uchar array1[LEN];
5 uchar array2[LEN];
6
7 void func2()
8 {
9 1 uchar i;
10 uchar *a,*b;
11 1 uint sum=0;
12 1
13 1 for(i=0,a=array1,b=array2;i<LEN;i++){
14 2 sum=sum+(*a)+(*b);
15 2 a++;b++;
16 2 }
17 1 }
MODULE INFORMATION:STATIC OVERLAYABLE
CODE SIZE= 95 ----
CONSTANT SIZE=---- ----
XDATA SIZE=---- ----
PDATA SIZE=---- ----
DATA SIZE=20 9
IDATA SIZE=---- ----
BIT SIZE=----- ---
END OF MODULE INFORMATION.
程序1的func1( )表明完成求和的数组中的数据是用数组的下标访问的。当用数组
的下标访问数组元素时,使用下面的过程计算地址:
· 得到array1[ ]的起始地址;
· 把数组的下标作为偏移量加到起始地址上。
这是计算地址的一般方法。在func1( )中,整个"for"循环当每次执行到这个语句时,
都必须进行地址的计算。而且,array2[ ]的地址也是用相同方法计算出来的。正像
前面所讲,数组元素放在存储器中的连续地址空间。因而,当以数字顺序访问数组
元素时,每次在数组的起始地址上加上偏移量是很花费时间的。
程序2的func2( )是使用指针访问数组。在"for"循环的初始部分把数组array1[ ]和
array2[ ]的起始地址赋给指针变量。在"for"循环中,指针用于计算,然后加1。这
种方法不须要在访问数组元素过程中计算地址。
使用指针和使用数组在功能上是等价的,具体使用哪种方法取决于产生代码的效率。
视频教程列表
文章教程搜索
C语言程序设计推荐教程
C语言程序设计热门教程
|