最近單片機課講到了定時計數器,在C語言中定時計數器的初值可以采用這種方式(假設計數10000次)TH0=(65536-10000)/256;TL0=(65536-10000)%256;這是通用的方法,65536-10000=55536=0xD8F0;賦值后TH0=0xD8,TL0=0xF0。我聯想到補碼的規則,65536-10000的數值在計算機中和-10000數據存儲是一樣的,于是我就簡單賦值為TH0=(-10000)/256.;TL0=(-10000)%256;可以少寫一個數據,減少敲字的工作。我就這樣給學生講了。這兩種方法都可以。
在一天李老師看到我的學生作業都是寫TH0=(-10000)/256.;TL0=(-10000)%256;她說-10000可能使用不對。當天晚上的時候在QQ上發消息過來說,經驗證,在Keil中,TH0=(65536-10000)/256;TL0=(65536-10000)%256;的賦值方式TH0=0xD8,TL0=0xF0。但是TH0=(-10000)/256.;TL0=(-10000)%256;的賦值方式TH0=0xD9,TL0=0xF0。TH0的數值總是要大1,而且取不同的數值驗證均是這個結果,兩種方式TH0總是相差1,而TL0數值是一樣的。我打開Keil,輸入程序,然后調試查看匯編指令,得到如下結果:
8: TH0=(65536-10000)/256;
C:0x009B 758CD8 MOV TH0(0x8C),#0xD8
9: TH1=(-10000)/256;
C:0x009E 758DD9 MOV TH1(0x8D),#0xD9
發現匯編指令直接對于TH0和TH1進行賦值,沒有經過任何的運算,但是就是相差1,這是為什么呢?我無法理解,后來在百度知道上提問,得到的回答是:這個和默認數據類型有關,TH0=(65536-10000)/256,默認unsigned char,即TH0=0xD8;TH0=(-10000)/256,默認signed char,二進制最高位為符號位,負數為1,所以TH0=0xD9 。
原來是Keil編譯器計算數據的時候默認的數據類型不一樣,65536-10000=55536是unsigned類型,55536/256=216=0xD8,而-10000是signed類型,(-10000)/256=-39=0xD9。原來如此,Keil的編譯器預先處理的時候根據不同類型的數據進行了不同的運算,然后直接賦值。我又驗證了一下,TH0=(unsigned int)(-10000)/256;發現先把-10000強制轉換為unsigned類型后,得到的結果就是正確的了TH0=0xD8。得到答案后臉紅了,不過多虧是在放假期間,沒有學生看到。開學后立即在課堂上更正了。╮(╯▽╰)╭,這次糗大了。
我重新寫了一個程序,#include<reg51.h>
void main()
{
unsigned int i;
unsigned char j;
i= - 10000;
j=i/256;
while(1)
;
}
中間加一個變量,看Keil會怎么處理,結果發現
2: void main()
3: {
4: unsigned int i;
5: unsigned char j;
6: i=-10000;
C:0x000F 7ED8 MOV R6,#0xD8
7: j=i/256;
8:
C:0x0011 8E08 MOV 0x08,R6
9: while(1)
C:0x0013 80FE SJMP C:0013
還是直接賦值,編譯器太聰明了,知道80C51對于數據運算非常非常的不擅長,于是直接處理完數據,然后用賦值的方式來寫匯編的指令。而且還知道,i的低字節沒有用到,在指令里根本沒有出現,這也太聰明了吧。Keil軟件是最流行,最好用的編譯器,不是浪得虛名的。
我再修改:
include<reg51.h>
void main()
{
unsigned int i;
unsigned char j;
i=-10000;
i++;
j=i/256;
while(1)
;
}
結果發現代碼只增加了一點。
2: void main()
3: {
4: unsigned int i;
5: unsigned char j;
6: i=-10000;
C:0x0003 7FF0 MOV R7,#B(0xF0)
C:0x0005 7ED8 MOV R6,#0xD8
7: i++;
C:0x0007 0F INC R7
C:0x0008 BF0001 CJNE R7,#0x00,C:000C
C:0x000B 0E INC R6
8: j=i/256;
9:
C:0x000C 8E08 MOV 0x08,R6
10: while(1)
C:0x000E 80FE SJMP C:000E
但對于j的運算還是用賦值的方式。我再改,把i類型變成signed類型,結果大吃一驚:
include<reg51.h>
void main()
{
int i;
unsigned char j;
i=-10000;
j=i/256;
while(1)
;
}
程序變得非常龐大,代碼從28B猛增到169B:
C:0x0000 02009D LJMP C:009D C?SIDIV: C:0x0003 C2D5 CLR F0(0xD0.5) C:0x0005 EC MOV A,R4 C:0x0006 30E709 JNB 0xE0.7,C:0012 C:0x0009 B2D5 CPL F0(0xD0.5) C:0x000B E4 CLR A C:0x000C C3 CLR C C:0x000D 9D SUBB A,R5 C:0x000E FD MOV R5,A C:0x000F E4 CLR A C:0x0010 9C SUBB A,R4 C:0x0011 FC MOV R4,A C:0x0012 EE MOV A,R6 C:0x0013 30E715 JNB 0xE0.7,C:002B C:0x0016 B2D5 CPL F0(0xD0.5) C:0x0018 E4 CLR A C:0x0019 C3 CLR C C:0x001A 9F SUBB A,R7 C:0x001B FF MOV R7,A C:0x001C E4 CLR A C:0x001D 9E SUBB A,R6 C:0x001E FE MOV R6,A C:0x001F 120039 LCALL C?UIDIV(C:0039) C:0x0022 C3 CLR C C:0x0023 E4 CLR A C:0x0024 9D SUBB A,R5 C:0x0025 FD MOV R5,A C:0x0026 E4 CLR A C:0x0027 9C SUBB A,R4 C:0x0028 FC MOV R4,A C:0x0029 8003 SJMP C:002E C:0x002B 120039 LCALL C?UIDIV(C:0039) C:0x002E 30D507 JNB F0(0xD0.5),C:0038 C:0x0031 C3 CLR C C:0x0032 E4 CLR A C:0x0033 9F SUBB A,R7 C:0x0034 FF MOV R7,A C:0x0035 E4 CLR A C:0x0036 9E SUBB A,R6 C:0x0037 FE MOV R6,A C:0x0038 22 RET C?UIDIV: C:0x0039 BC000B CJNE R4,#0x00,C:0047 C:0x003C BE0029 CJNE R6,#0x00,C:0068 C:0x003F EF MOV A,R7 C:0x0040 8DF0 MOV B(0xF0),R5 C:0x0042 84 DIV AB C:0x0043 FF MOV R7,A C:0x0044 ADF0 MOV R5,B(0xF0) C:0x0046 22 RET C:0x0047 E4 CLR A C:0x0048 CC XCH A,R4 C:0x0049 F8 MOV R0,A C:0x004A 75F008 MOV B(0xF0),#0x08 C:0x004D EF MOV A,R7 C:0x004E 2F ADD A,R7 C:0x004F FF MOV R7,A C:0x0050 EE MOV A,R6 C:0x0051 33 RLC A C:0x0052 FE MOV R6,A C:0x0053 EC MOV A,R4 C:0x0054 33 RLC A C:0x0055 FC MOV R4,A C:0x0056 EE MOV A,R6 C:0x0057 9D SUBB A,R5 C:0x0058 EC MOV A,R4 C:0x0059 98 SUBB A,R0 C:0x005A 4005 JC C:0061 C:0x005C FC MOV R4,A C:0x005D EE MOV A,R6 C:0x005E 9D SUBB A,R5 C:0x005F FE MOV R6,A C:0x0060 0F INC R7 C:0x0061 D5F0E9 DJNZ B(0xF0),C:004D C:0x0064 E4 CLR A C:0x0065 CE XCH A,R6 C:0x0066 FD MOV R5,A C:0x0067 22 RET C:0x0068 ED MOV A,R5 C:0x0069 F8 MOV R0,A C:0x006A F5F0 MOV B(0xF0),A C:0x006C EE MOV A,R6 C:0x006D 84 DIV AB C:0x006E 20D21C JB OV(0xD0.2),C:008D C:0x0071 FE MOV R6,A C:0x0072 ADF0 MOV R5,B(0xF0) C:0x0074 75F008 MOV B(0xF0),#0x08 C:0x0077 EF MOV A,R7 C:0x0078 2F ADD A,R7 C:0x0079 FF MOV R7,A C:0x007A ED MOV A,R5 C:0x007B 33 RLC A C:0x007C FD MOV R5,A C:0x007D 4007 JC C:0086 C:0x007F 98 SUBB A,R0 C:0x0080 5006 JNC C:0088 C:0x0082 D5F0F2 DJNZ B(0xF0),C:0077 C:0x0085 22 RET C:0x0086 C3 CLR C C:0x0087 98 SUBB A,R0 C:0x0088 FD MOV R5,A C:0x0089 0F INC R7 C:0x008A D5F0EA DJNZ B(0xF0),C:0077 C:0x008D 22 RET
2: void main()
3: {
4: int i;
5: unsigned char j;
6: i=-10000;
7:
C:0x008E 7FF0 MOV R7,#B(0xF0)
C:0x0090 7ED8 MOV R6,#0xD8
8: j=i/256;
9:
C:0x0092 7C01 MOV R4,#0x01
C:0x0094 7D00 MOV R5,#0x00
C:0x0096 120003 LCALL C?SIDIV(C:0003)
C:0x0099 8F08 MOV 0x08,R7
10: while(1)
C:0x009B 80FE SJMP C:009B
就是一個signed和unsigned的區別,用的著差別這么大嗎?
通過以上的實驗,可以得出結論:Keil編譯器非常智能,會生成最短的代碼,能夠智能判斷每個變量的使用,生成最短的代碼。同時,學習單片機的各位同仁,除非萬不得已,千萬不要用signed類型。