研究试验5 接收不定数量的参数
2012-12-21
a.c
1 void showchar(char a,int b); 2 main() 3 { 4 showchar('a',2); 5 } 6 void showchar(char a,int b) 7 { 8 *(char far*)(0xb8000000+160*10+80)=a; 9 *(char far*)(0xb8000000+160*10+81)=b; 10 }
使用TC2.0进行编译的,借用上个研究试验的c0s.obj,下面的汇编代码:
184D:0000 B84518 MOV AX,1845
184D:0003 8ED8 MOV DS,AX
184D:0005 8ED0 MOV SS,AX
184D:0007 BC8000 MOV SP,0080
184D:000A E80500 CALL 0012 ;调用main函数 push ip(000D) jmp 0012 bp=0000
sp->007d | 0d |
007f | 00 |
0080 |
;main function start
184D:0012 55 PUSH BP;暂存bp,实际上是保存调用main的函数时栈的初始状态
sp->007C | 00 |
007D | 00 |
007E | 0d |
007F | 00 |
0080 |
184D:0013 8BEC MOV BP,SP ;bp=007c 保存main函数的栈初始状态
184D:0015 B80200 MOV AX,0002
184D:0018 50 PUSH AX
sp->007A | 02 |
007B | 00 |
007c | 00 |
007d | 00 |
007e | 0d |
007f | 00 |
0080 |
184D:0019 B061 MOV AL,61
184D:001B 50 PUSH AX
sp->0078 | 61 |
0079 | 00 |
007A | 02 |
007B | 00 |
007C | 00 |
007D | 00 |
007E | 0d |
007F | 00 |
0080 |
参数 'a'
参数2
184D:001C E80400 CALL 0023;push ip(0017) jmp 0023
sp->0076 | 1f |
0077 | 00 |
0078 | 61 |
0079 | 00 |
007A | 02 |
007B | 00 |
007C | 00 |
007D | 00 |
007E | 0d |
007F | 00 |
0080 |
;showchar function start
-u cs:0023
184D:0023 55 PUSH BP
sp->0074 | 7C |
0075 | 00 |
0076 | 1F |
0077 | 00 |
0078 | 61 |
0079 | 00 |
007A | 02 |
007B | 00 |
007C | 00 |
007D | 00 |
007E | 0d |
007F | 00 |
0080 |
184D:0024 8BEC MOV BP,SP;栈的初始状态 bp=sp=0074
184D:0026 8A4604 MOV AL,[BP+04] ;ds=ss,ss:[0078] (AL)=61H
184D:0029 BB00B8 MOV BX,B800
184D:002C 8EC3 MOV ES,BX
184D:002E BB9006 MOV BX,0690
184D:0031 26 ES:
184D:0032 8807 MOV [BX],AL ; (B800:0690)=61H
184D:0034 8A4606 MOV AL,[BP+06]
184D:0037 BB00B8 MOV BX,B800
184D:003A 8EC3 MOV ES,BX
184D:003C BB9106 MOV BX,0691
184D:003F 26 ES:
184D:0040 8807 MOV [BX],AL
184D:0042 5D POP BP ;释放栈中的内存
sp->0076 | 1F |
0077 | 00 |
0078 | 61 |
0079 | 00 |
007A | 02 |
007B | 00 |
007C | 00 |
007D | 00 |
007E | 0d |
007F | 00 |
0080 | |
184D:0043 C3 RET ;pop ip(ip=001F)
;showchar function end
sp->0078 | 61 |
0079 | 00 |
007A | 02 |
007B | 00 |
007C | 00 |
007D | 00 |
007E | 0d |
007F | 00 |
0080 |
184D:001F 59 POP CX ;释放内存
184D:0020 59 POP CX ;释放内存
184D:0021 5D POP BP
184D:0022 C3 RET
;main function end
184D:000D B8004C MOV AX,4C00
184D:0010 CD21 INT 21
总结:1)参数使用栈进行传递,参数入栈的顺序是从右到左,最左边的参数是在栈顶
2)char虽然是占用一个字节,但是仍然采用是用16位存储入栈,存在低位
3)bp寄存器存放进入函数之前的栈顶地址,通过入栈的次数可以定位参数在栈中的位置。
4)参数占用内存采用出栈的方式释放,不是采用直接移动栈顶地址,区别于局部变量
5)注意在利用栈顶定位参数的时候不要落下call和ret指令,call会调用push指令,ret会调用pop指令
6)*(char far*)(0xb8000000+160*10+80)=a;表示地址为B800:0690H的内存单元是放入a变量的内容。
----------------------------------------------------------------------
利用BP寄存器定位参数的修改程序如下:
1 void showchar(char a,int b); 2 main() 3 { 4 showchar('a',2); 5 } 6 void showchar(char a,int b) 7 { 8 *(char far*)(0xb8000000+160*10+80)=*(char *)(_BP+4); 9 *(char far*)(0xb8000000+160*10+81)=*(char *)(_BP+6); 10 }
-----------------------------------------------------------------
数量不定参数的接收
B.C
1 void showchar(int,int,...); 2 main() 3 { 4 showchar(8,2,'a','b','c','d','e','f','g','h'); 5 } 6 7 void showchar(int n,int color,...) 8 { 9 int a; 10 for(a=0;a!=n;a++) 11 { 12 *(char far *)(0xb8000000+160*10+80+a+a)=*(int *)(_BP+8+a+a); 13 *(char far *)(0xb8000000+160*10+81+a+a)=color; 14 } 15 }
反汇编
1 -u cs:1fa 2 ;main 函数 start 3 1847:01FA 55 PUSH BP 4 1847:01FB 8BEC MOV BP,SP 5 6 ;10个参数 用栈传递 占用20个内存单元 7 1847:01FD B86800 MOV AX,0068 8 1847:0200 50 PUSH AX 9 1847:0201 B86700 MOV AX,0067 10 1847:0204 50 PUSH AX 11 1847:0205 B86600 MOV AX,0066 12 1847:0208 50 PUSH AX 13 1847:0209 B86500 MOV AX,0065 14 1847:020C 50 PUSH AX 15 1847:020D B86400 MOV AX,0064 16 1847:0210 50 PUSH AX 17 1847:0211 B86300 MOV AX,0063 18 1847:0214 50 PUSH AX 19 1847:0215 B86200 MOV AX,0062 20 1847:0218 50 PUSH AX 21 1847:0219 B86100 MOV AX,0061 22 1847:021C 50 PUSH AX 23 1847:021D B80200 MOV AX,0002 24 1847:0220 50 PUSH AX 25 1847:0221 B80800 MOV AX,0008 26 1847:0224 50 PUSH AX 27 28 1847:0225 E80500 CALL 022D 29 30 ;调用showchar函数 31 1847:022D 55 PUSH BP 32 1847:022E 8BEC MOV BP,SP 33 1847:0230 56 PUSH SI 34 1847:0231 33F6 XOR SI,SI ;si清零,比mov si,0的效率高,si相当于计数器。 35 1847:0233 EB49 JMP 027E 36 ;*(int *)(_BP+8+a+a) 37 1847:0235 8BDD MOV BX,BP ;_BP+8+a+a 38 1847:0237 03DE ADD BX,SI ; 39 1847:0239 03DE ADD BX,SI ; 40 1847:023B 83C308 ADD BX,+08 ;移动到存放61H内存单元的 41 1847:023E 8A07 MOV AL,[BX] ;将'a'赋值给al 42 1847:0240 50 PUSH AX ;字母入栈 43 44 ;*(char far *)(0xb8000000+160*10+80+a+a) 45 1847:0241 8BC6 MOV AX,SI 46 1847:0243 99 CWD ;扩展ax的符号位,即将ax的15位扩展到dx中。如果ax的15位是0,表面ax内存储负数,dx变为0000H;若ax的15位是1,dx变为1111H 47 1847:0244 52 PUSH DX 48 1847:0245 50 PUSH AX 49 1847:0246 8BC6 MOV AX,SI 50 1847:0248 99 CWD 51 1847:0249 5B POP BX ;(BX)=(AX)=(SI) 52 1847:024A 59 POP CX ;(CX)=(DX)=AX 15位 53 1847:024B 03D8 ADD BX,AX ;add si,2 移动2位 54 1847:024D 13CA ADC CX,DX ;这么多行实现a+a,有何特殊之处? 55 56 57 1847:024F 81C39006 ADD BX,0690 ;160*10+80 58 1847:0253 81D100B8 ADC CX,B800 59 1847:0257 8EC1 MOV ES,CX 60 1847:0259 58 POP AX 61 1847:025A 26 ES: 62 1847:025B 8807 MOV [BX],AL ; 63 64 *(char far *)(0xb8000000+160*10+81+a+a)=color 65 1847:025D 8A4606 MOV AL,[BP+06] 66 1847:0260 50 PUSH AX 67 1847:0261 8BC6 MOV AX,SI 68 1847:0263 99 CWD 69 1847:0264 52 PUSH DX 70 1847:0265 50 PUSH AX 71 1847:0266 8BC6 MOV AX,SI 72 1847:0268 99 CWD 73 1847:0269 5B POP BX 74 1847:026A 59 POP CX 75 1847:026B 03D8 ADD BX,AX 76 1847:026D 13CA ADC CX,DX 77 78 1847:026F 81C39106 ADD BX,0691 79 1847:0273 81D100B8 ADC CX,B800 80 1847:0277 8EC1 MOV ES,CX 81 1847:0279 58 POP AX 82 1847:027A 26 ES: 83 1847:027B 8807 MOV [BX],AL 84 1847:027D 46 INC SI 85 86 1847:027E 3B7604 CMP SI,[BP+04] ;a与n比较 87 1847:0281 75B2 JNZ 0235 ;不相等 不等于0就跳到0235 88 89 1847:0283 5E POP SI 90 1847:0284 5D POP BP 91 1847:0285 C3 RET 92 93 94 1847:0228 83C414 ADD SP,+14 95 1847:022B 5D POP BP 96 1847:022C C3 RET
栈 | 内容 | 指令 | CS:IP | |
FFC8 | 00 | push ax | ||
FFC9 | 00 | |||
FFCA | 00 | push dx | ||
FFCB | 00 | |||
FFCC | 61 | push ax | ||
FFCD | 00 | |||
FFCE | B0 | push si | 1847:0230 | |
FFCF | 05 | |||
FFD0 | E8 | push BP | BP=FFD0 | 1847:022D |
FFD1 | FF | |||
FFD2 | 28 | push ip 0228 | call 022D | |
FFD3 | 02 | |||
FFD4 | 08 | push 0008 | 字符个数参数 | 1847:0224 |
FFD5 | 00 | |||
FFD6 | 02 | push 0002 | 颜色参数 | 1847:0220 |
FFD7 | 00 | |||
FFD8 | 61 | push 0061 | 1847:021C | |
FFD9 | 00 | |||
FFDA | 62 | push 0062 | 1847:0218 | |
FFDB | 00 | |||
FFDC | 63 | push 0063 | 1847:0214 | |
FFDD | 00 | |||
FFDE | 64 | push 0064 | 1847:0210 | |
FFDF | 00 | |||
FFE0 | 65 | push 0065 | 1847:020C | |
FFE1 | 00 | |||
FFE2 | 66 | push 0066 | 1847:0208 | |
FFE3 | 00 | |||
FFE4 | 67 | push 0067 | 1847:0204 | |
FFE5 | 00 | |||
FFE6 | 68 | push 0068 | 1847:0200 | |
FFE7 | 00 | |||
FFE8 | F4 | push bp | 1847:01FA | |
FFE9 | FF | |||
FFEA | 83 | call 1FA | 1847:011A | |
FFEB | 02 |
------------------------------
对比上面两个showchar函数,可以发现showchar都是通过栈来传递参数的。
push bp
mov bp,sp ;001
mov ax,参数1
push ax
mov ax,参数2
push ax
mov ax,参数3
push ax
.........
call showchar 函数
push bp
mov bp,sp ;002 ;
001和002的两个bp数值差额是参数个数*2+2(call showchar->push ip)+2(push bp)=2*N+2+2(N是参数的个数)
上面三张图分别是a.exe的反汇编指令、main函数将实参传递、调用showchar函数。
对比就可以发现实参传递和调用showchar之后bp的数值差是8。
可以再通过其他的函数来验证。
当然上述规律针对的是没有返回值的函数,如果有返回值了,那可能就不适应了,不过按照这个思路进行探讨。
--------------------------------------------
printf函数
pt1.c pt2.c pt3.c
1 main() 2 { 3 printf("%c%c",'a','b'); 4 }
1 main() 2 { 3 printf("%c%d",'a',12); 4 }
1 main() 2 { 3 printf("%c is the first letter!",'a'); 4 }
以上三段代码是三个关于printf应用的例子。
根据上面得到的结论,参数都是利用栈进行传递的。三个程序中分别传递了2、2、1个参数。共同之处就是都使用了mov ax,0194进行参数传递,那么0194是什么呢?
三个程序中0194都是对应着一段字符串,而且字符串的结尾处是0。
所以可以断定,printf函数将"..."中的内容由编译器存放到一个地址,根据格式符号%及其后面所跟的字符进行判定,如果是c或者是d,就将栈的内容取出进行替换,再将未替换部分直接拷到到屏幕。
----------------------------------------
实现%c %d功能的函数print
1 void print(int,char *str,...); 2 main() 3 { 4 int row=8; //显示行号 5 print(row,"%c%c",'a','b'); //注意"%c%c",放入内存的部分是%c%c0,详见C基础 6 row++; 7 print(row,"%c%d",'a',123); 8 row++; 9 print(row,"%c is the first letter",'a'); 10 11 } 12 13 void print(int row,char *str,...) 14 { 15 char ch; //判断字符 16 int ch_count=0; //字符数目统计 17 18 19 int stackstep=0; //栈移动参数 20 int showstep=0; //显示移动参数 21 22 int q; //数值除10的商 23 int pushcount; //模拟入栈的数目统计 24 25 ch=str[ch_count++]; 26 27 while(ch) 28 { 29 //先判断字符 30 if ('%' == ch) 31 { 32 ch=str[ch_count++]; //下一个字符判断 33 switch(ch) 34 { 35 //%c形式,直接将字符参数放入显存 36 case 'c': 37 *(char far *)(0xb8000000+160*row+20+(showstep++))=*(int *)(_BP+8+stackstep); 38 *(char far *)(0xb8000000+160*row+20+(showstep++))=2;//绿色显示 39 break; 40 case 'd': 41 pushcount=0; 42 q = *(int *)(_BP+8+stackstep); //将栈中的整数参数赋值给q 43 stackstep+=2; 44 45 //特殊整数的处理 46 if(!q) 47 { 48 *(char far *)(0xb8000000+160*row+20+(showstep++))='0'; 49 *(char far *)(0xb8000000+160*row+20+(showstep++))=2; 50 } 51 52 while(q) 53 { 54 //模拟入栈 55 _SP-=2; 56 *(int *)(_SP)=(q%10)+48; 57 58 pushcount++; 59 q/=10; 60 } 61 62 while(pushcount) 63 { 64 //模拟出栈 65 *(char far *)(0xb8000000+160*row+20+(showstep++))=*(int *)(_SP); 66 *(char far *)(0xb8000000+160*row+20+(showstep++))=2; 67 _SP+=2; 68 69 pushcount--; 70 } 71 break; 72 } 73 } 74 75 76 else 77 { 78 *(char far *)(0xb8000000+160*row+20+(showstep++))=ch; 79 *(char far *)(0xb8000000+160*row+20+(showstep++))=2; 80 } 81 ch=str[ch_count++]; 82 } 83 84 85 }