《汇编语言》第10章 call和ret指令
call和ret指令都是转移指令,经们都修改IP,或同时修改CS和IP。它们经常被共同用来实现子程序的设计。这一章,我们讲解call和ret指令的原理。
10.1 ret和retf
ret指令用栈中的数据,修改IP的内容,从而实现近转移;
retf指令用栈中的数据,修改cs和ip的内容,从而实现远转移。
CPU执行ret指令时,进行下面两步操作;
(1)(IP)=((ss)*16+(sp))
(2)(sp)=(sp)+2
CPU执行ret指令时,进行下面4步操作;
(1)(IP)=((ss)*16+(sp))
(2)(sp)=(sp)+2
(3)(CS)=((ss)*16+(sp))
(4)(sp)=(sp)+2
可以看出,如果我们用汇编语法来解释ret和retf指令,则:
CPU执行ret指令时,相当于进地:
pop IP
CPU执行rett指令时,相当于进地:
pop IP
pop CS
例:
下面的程序中,ret指令执行后,(IP)=0,cs:ip指向代码的第一条指令。
assume cs:code
stack segmentdb 16 dup (0)
stack ends
code segmentmov ax,4c00hint 21hstart: mov ax,stackmov ss,ax mov sp,16mov ax,0push ax mov bx,0ret
code ends
end start
调试
由以上可以看出,执行ret命令后,(IP)=0,CS:IP指向代码段的第一条指令。
下面的程序中,retf指令执行后,CS:IP指向代码的第一条指令。
;t10_2.asm
assume cs:code
stack segmentdb 16 dup (0)
stack ends
code segmentmov ax,4c00hint 21h
start: mov ax,stackmov ss,axmov sp,16mov ax,0push cspush axmov bx,0retf
code ends
end start
debug调试
检测点10.1
补全程序,实现从内存1000:0000处开始执行指令。
;t10_j1.asm
assume cs:code
stack segmentdb 16 dup (0)
stack ends
code segment
start: mov ax, stack mov ss,ax mov sp,16mov ax,1000h ;把1000传给ax,执行pop命令时会把这个赋给CSpush axmov ax,0h ;把0传给ax,执行pop命令时会把这个值赋给IP push ax retf
code ends
end start
debug调试:
10.2 call指令
CPU执行call指令时,进行两步操作:
(1)将当前的IP或CS和IP压入栈中;
(2)转移。
call指令不能实现短转移,除此之外,call指令实现转移的方法和jmp指令的原理相同,下面的几个小节中,
我们以给出转移目的地址的不同方法为主线,讲解call指令的主要应用格式。
10.3 依据位移进行转移的call指令
call标号(将当前的IP压栈后,转到标号处执行指令)
CPU执行此种格式的call指令时,进行如下的操作:
(1) (sp)==(sp)-2
((ss)*16 + (sp))=(IP)
(2) (IP)=(IP)+16位位移
16位位移=标号处的地址-call指令后的第一个字节的地址;
16位位移的范围为-32768~32767,用补码表示;
16位位移由编译程序在编译时算出。
从上面的描述中,可以看出,如果我们用汇编语法来解释此种格式的call指令,则:
CPU执行“call 标号”时,相当于进行:
push IP
jmp near ptr 标号
检测点10.2
下面的程序执行后,ax中的数值为多少?
内存地址 机器码 汇编指令
1000:0 b8 00 00 mov ax,0
1000:3 e8 01 00 call s
1000:6 40 inc ax
1000:7 58 s:pop ax
debug调试:
由实验结果得到ax=6
10.4 转移的目的地址在指令中的call指令
前面讲的call指令,其对应的机器指令中并没有转移的目的地址,而是相对于当前IP的转移位移。
“call far ptr 标号”实现的是段间转移。
CPU执行此种格式的call指令时,进行如下的操作。
(1)(sp)=(sp)-2
((ss)*16+(sp))=(CS)
(sp)=(sp)-2
((ss)*16+(sp))=(IP)
(2)(CS)=标号所在段的段地址
(IP)=标号在段中的偏移地址
从上面的描述中可以看出,如果我们用汇编语法来解释此种格式的call指令,则:
CPU执行"call far ptr 标号"时,相当于进行:
push CS
push IP
jmp far ptr 标号
检测点10.3
下面的程序执行后,ax中的数值为多少?
内存地址 机器码 汇编指令
1000:0 b8 00 00 mov ax,0
1000:3 9A 09 00 00 00 call far ptr s ; 执行完后CS=1000,IP=8压入栈
1000:8 40 inc ax
1000:9 58 s:pop ax ; ax=IP,即8
add ax,ax ; ax=10h
pop bx ; bx=cs即1000
add ax,bx ; ax=1010h
实验验证:
验证结果:ax=1010h
10.5 转移地址在寄存器中的call指令
指令格式:call 16位reg
功能:
(sp)=(sp)-2
((ss)*16+(sp))=(IP)
(IP)=(16位reg)
用汇编语法来解释此种格式的call指令,CPU执行“call 16位reg”时,相当于进行:
push IP
jmp 16位reg
检测点10.4
下面的程序执行后,ax中的数值为多少?
内存地址 机器码 汇编指令
1000:0 b8 06 00 mov ax,6 ;ax=6
1000:3 ff d0 call ax ;ss:sp=ip =>5
1000:5 40 inc ax
1000:6 8b ec mov bp,sp ;bp = sp
03 46 00 add ax,[bp] ;相当于 ax = ax + ss:sp===>6+5 = 11
程序执行后,ax=11即十六进制b
10.6 转移地址在内存中的call指令
转移地址在内存中的call指令有两种格式。
(1)call word ptr内存单元地址
用汇编语法来解释此种格式的call指令,则:
CPU执行”call word ptr内存单元地址“时,相当于进行:
push IP
jmp word ptr内存单元地址
比如,下面的指令:
mov sp,10h
mov ax,0123h
mov ds:[0],ax
call word ptr ds:[0]
执行后,(IP)=0123H,(sp)=0EH。
(2)call dword ptr内存单元地址
用汇编语法来解释此种格式的call指令,则:
CPU执行”call dword ptr内存单元地址“时,相当于进行:
push CS
push IP
jmp dword ptr 内存单元地址
比如,下面的指令:
mov sp, 10h
mov ax, 0123h
mov ds:[0], ax
mov word ptr ds:[2], 0
call dword ptr ds:[0]
执行后,(CS)=0,(IP)=0123h,(sp)=0CH.
检测点10.5
(1)下面的程序执行后,ax中的数值为多少?(注意:用call指令的原理来分析,不要在debug中
单步跟踪来验证你的结论。对于此程序,在debug中单步跟踪的结果,不能代表CPU的实际执行结果。)
assume cs:code
stack segmentdw 8 dup (0)
stack ends
code segmentstart: mov ax,stackmov ss,axmov sp,16mov ds,axmov ax,0call word ptr ds:[0EH]inc axinc axinc axmov ax,4c00hint 21h
code ends
end start
执行call word ptr ds:[0eh]指令时,先cs入栈,再ip=11入栈,最后ip转移到(ds:[0eh])。(ds:[0eh])=11h,执行inc ax……最终ax=3
题中特别关照别用debug跟踪,跟踪结果不一定正确,但还是忍不住去试试,看是什么结果。
(2)下面的程序执行后,ax和bx中的数值为多少?
assume cs:code
data segmentdw 8 dup (0)
data ends
code segmentstart: mov ax,datamov ss, axmov sp, 16mov word ptr ss:[0],offset smov ss:[2], cscall dword ptr ss:[0]nops: mov ax, offset ssub ax, ss:[0cH]mov bx, cssub bx, ss:[0eH]mov ax, 4c00hint 21h
code ends
end start
debug调试
以前结果显示ax = 1, bx = 0
10.7 call和ret的配合使用
前面,我们已经分别学习了ret和call指令的原理。现在来看一下,如何将它们配合使用来实现子程序的机制。
问题10.1
下面程序返回前,bx中的值是多少?
assume cs:code
code segmentstart: mov ax, 1mov cx, 3call smov bx, ax ;(bx)=?mov ax, 4c00hint 21hs: add ax, axloop sret
code ends
end start
思考后看分析。
分析:
我们来看一下CPU执行这个程序的主要过程。
(1)CPU将call s指令的机器码读入,IP指向了call s后的指令mov bx, ax,然后CPU执行call s指令,
将当前的IP值(指令mov bx,ax的偏移地址)压栈,并将IP的值改变为标号s处的偏移地址:
(2)CPU从标号s处开始执行指令,loop循环完毕后,(ax)=8;
(3)CPU将ret指令的机器码读入,IP指向了ret指令后的内存单元,然后CPU执行ret指令,
从栈中弹出一个值(即call s先前压入的mov bx, ax指令的偏移地址)送入IP中。则cs:ip指向指令mov bx,ax;
(4)CPU从mov bx,ax开始执行指令,直至完成。
程序返回前,(bx)=8。可以看出,从标号s到ret的程序段的作用是计算2的N次方,计算前,N的值由cx提供。
我们再来看下面的程序:
源程序 内存中的情况(假设程序从内存1000:0处装入)
assume cs:code
stack segment
db 8 dup (0) 1000:0000 00 00 00 00 00 00 00 00
db 8 dup (0) 1000:0008 00 00 00 00 00 00 00 00
stack ends
code segment
start: mov ax, stack 1001:0000 b8 00 10
mov ss, ax 1001:0003 8e d0
mov sp, 16 1001,0005 bc 10 00
mov ax, 1000 1001:0008 b8 e8 03
call s 1001:000b b8 00 4c
mov ax, 4c00h 1001:000e b8 00 4c
int 21h 1001:0011 cd 21
s: add ax,ax 1001:0013 03 c0
ret 1001:0015 c3
code ends
end start
看一下程序的主要执行过程。
(1)前3条指令执行后,栈的情况如下:
1000:0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
↑
ss:sp
(2)call指令读入后,(IP)=000EH,CPU指令缓冲器中的代码为:E8 05 00;
CPU执行E8 05 00,首先,栈中的情况变为:
1000:0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0E 00
↑
ss:sp
然后,(IP)=(IP)+0005=0013H.
(3)CPU从cs:0013H处(即标号s处)开始执行。
(4)ret指令读入后:
(IP)=0016,CPU指令缓冲器中的代码为:C3
CPU执行C3,相当于进行pop IP,执行后,栈中的情况为:
1000:0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0E 00
↑
ss:sp
(IP)=000EH
(5)CPU回到cs:000EH处(即call指令后面的指令处)继续执行。
从上面的讨论中我们发现,可以写一个具有一定功能的程序段,我们 称其为子程序,
在需要的时候,用call指令转去执行。可是执行完成子程序后,如何让CPU接着call指令向下执行?
call指令转去执行子程序之前,call指令后面的指令的地址将存储在栈中,所以可在子程序的后面使用ret指令,
用栈中的数据设置IP的值,从而转到call指令后面的代码处理继续执行。
这样,我们可以利用call和ret来实现子程序的机制。子程序的框架如下。
标号:
指令
ret
具有子程序的源程序的框架如下。
assume cs:code
code segment
main: ;
;
call sub1 ;调用子程序sub1
;
;
mov ax,4c00h
int 21h
sub1: ; ;子程序sub1开始
;
call sub2 ;调用子程序sub2
;
;
ret ;子程序返回
sub2: ; ;子程序sub2开始
;
;
ret ;子程序返回
code ends
end main
现在,可以从子程序的角度,回过头来再看一下本节中的两个程序。
10.8 mul指令
因下面要用到,这里介绍一下mul指令,mul是乘法指令,使用mul做乘法的时候,注意以下两点。
(1)两个相乘的数:两个相乘的数:两个相乘的数,要么都是8位,要么都是16位,如果是8位,
一个默认放在AL中,另一个放在8位reg或内存字节单元中;如果是16位,一个默认在AX中,另一个
放在16位reg或内存字单元中。
(2)结果:如果是8位乘法,结果默认入在AX中;如果是16位乘法,结果高位默认在DX中存放,低位在AX中放。
格式如下:
mul reg
mul 内存单元
内存单元可以用不同的寻址方式给出,比如:
mul byte ptr ds:[0]
含义:(ax)=(al)*((ds)*16+0);
mul word ptr [bx+si+8]
含义:(ax)=(ax)*((ds)*16+(bx)+(si)+8)结果的低16位。
(dx)=(ax)*((ds)*16+(bx)+(si)+8)结果的高16位。
例:
(1)计算100*10.
100和10小于255,可以做8位乘法,程序如下。
mov al,100
mov bl,10
mul bl
结果:(ax)=1000(03e8H)
(2)计算100*10000
100小于255,可10000大于255,所以必须做16位乘法,程序如下。
mov ax,100
mov bx,10000
mul bx
结果:(ax)=4240H,(dx)=000FH (F4240H=1000000)
10.9 模块化程序设计
从上面我们看到,call与ret指令共同支持了汇编语言编程中的模块化设计。在实际编程中,程序的模块化是必不可少的。因为现实的问题比较复杂,对现实问题进行分析时,把它转化成为相互联系、不同层次的子问题,是必须的解决方法。而call与ret指令,我们可以用简捷的方法,实现多个相互联系、功能独立的子程序来解决一个复杂的问题。
下面的内容中,我们来看一下子程序设计中的相关问题和解决方法。
10.10参数和结果传递的问题
子程序一般都要根据提供的参数处理一定的事务,处理后,将结果(返回值)提供给调用者。其实,我们讨论参数和返回值传递的问题,
实际上就是在探讨,应该如何存储子程序需要的参数和产生的返回值 。
比如,设计一个子程序,可以根据提供的N,来计算N的3次方。
这里面就有两个问题:
(1)将参数N存储在什么地方?
(2)计算得到的数值,存储在什么地方?
很显然,可以用寄存器来存储,可以将参数放到bx中;因为子程序中要计算N*N*N,可以使用多个mul指令,
为了方便,可将结果放到dx和ax中。子程序如下。
;说明:计算N的3次方
;参数:(bx)=N
;结果:(dx:ax)=N^3
cube: mov ax,bx
mul bx
mul bx
ret
注意,我们在编程的时候要注意形成良好的内格,对于程序应有详细的注释。子程序的注释信息应该包含对子程序的功能、
参数和结果的说明。因为今天写的子程序,以后可能还会用到:自已写的子程序,也很可能要给别人使用,所以一定要有全在的说明。
用寄存器存储参数和结果是最常使用的方法。对于存放参数的寄存器和存放结果的寄存器,调用者和子程序的读写操作恰恰相反;
调用者将参数送入参数寄存器,从结果寄存器中取到返回值;子程序从参数寄存器中取到参数,将返回值送入结果寄存器。
编程,计算data段中第一组数据的3次方,结果保存承后面一组dword单元中。
assume cs:code
data segment
dw 1,2,3,4,5,6,7,8
dd 0,0,0,0,0,0,0,0
data ends
我们可以用到已经写好的子程序,程序如下:
assume cs:code
data segmentdw 1,2,3,4,5,6,7,8dd 0,0,0,0,0,0,0,0
data endscode segment
start: mov ax,datamov ds,axmov si,0 ;ds:si指向第一组word单元mov di,16 ;ds:di指向第二组dword单元mov cx,8s: mov bx,[si]call cubemov [di],axmov [di].2, dxadd si,2 ;ds:si指向下一个word单元add di,4 ;ds:di指向下一个dword单元loop smov ax,4c00hint 21h
cube: mov ax,bxmul bxmul bxret
code ends
end start
debug调试
10.11 批量数据的传递
前面的例程中,子程序中cube只有一个参数,放在bx中,如果有两个参数,那么可以用两个寄存器来放,可是如果需要传递的数据有3个、4个或更多直至N个,该怎样存放呢?寄存器的数量终究有限,我们不可能简单地用寄存器来存放多个需要传递的数据。对于返回值,也有同样的问题。
在这种时候,我们将批量数据放到内存中,然后将它们所在内存空间的首地址放在寄存器中,传递给需要的子程序。
对于具有批量数据的返回结果,也可用同样的方法。
下面看一个例子,设计一个子程序,功能:将一个全是字母的字符串转化为大写。
这个子程序需要知道两件事,字符串的内容和字符串的长度。因为字符串中的字母可能很多,所以不便将整个字符串中的所有字母都直接传递给子程序。但是,可以将字符串在内存中的首地址放在寄存器中传递给子程序。因为子程序中要用到循环,我们可以用loop指令,而循环的次数恰恰就是字符串的长度。出于方便的考虑可以将字会串的长度放到cx中。
capital:and byte ptr [si],11011111b ;将ds:si所指单元中的字母转化为大写
inc si ;ds:si指向下一个单元
loop capital
ret
编程,将data段中的字符串转化为大写。
assume cs:code
data segmentdb 'conversation'
data ends
code segmentstart: mov ax, datamov ds, axmov si, 0 ;ds:si指向字符串(批量数据)所在空间的首地址mov cx, 12 ;cx存放字符串的长度call capitalmov ax, 4c00hint 21h
capital: and byte ptr [si], 11011111binc siloop capitalret
code ends
end start
debug调试:
注意,除了用寄存器传递参数外,还有一种通用的方法是用栈来传递参数。关于这种技术请参看附注4.
10.12 寄存器冲突的问题
设计一个子程序,功能:将一个全是字母,以0结尾的字符串,转化为大写。
程序要处理的字符串以0作为结尾符,这个字符串可以如下定义;
db 'conversation', 0
应用这个子程序,字符串的内容后面一定要有一个0,标记字符串的结束。子程序可以依次读取字符进行检测,
如果不是0,就进行大写的转化;如果是0,就结束处理。由于可通过检测0而知道是否已经处理完整个字符串,
所以子程序可以不需要字符串的长度作为参数。可以用jcxz来检测0。
;说明:将一个全是字母,以0结尾的字符串,转化为大写。
;参数:ds:si指向字符串的首地址
;结果:没有返回值
capital:mov cl,[si]
mov ch,0
jcxz ok ;如果(cx)=0,结束;如果不是0,处理
and byte ptr [si],11011111b ;ds:si所指单元中的字母转化为大写
inc si ;ds:si指向下一个单元
jmp short capital
ok: ret
来看一下这个子程序的应用。
(1)将data段中字符串转化为大写。
assume cs:code
data segment
db 'conversation', 0
data ends
代码段中的相关程序段如下。
mov ax, data
mov ds, ax
mov si, 0
call capital
(2)将data段中的字符串全部转化为大写。
assume cs:code
data segment
db 'word', 0
db 'unix', 0
db 'wind', 0
db 'good', 0
data ends
可以看到,所有字符串的长度都是5(算上结尾符0),使用循环,重复调用子程序capital,完成对4个字符串的处理。
完整的程序如下。
code segmentstart:mov ax, datamov ds, axmov bx, 0mov cx, 4s: mov si, bxcall capitaladd bx, 5loop smov ax, 4c00hint 21h
capital:mov cl, [si]mov ch, 0jcxz okand byte ptr [si], 11011111binc sijmp short capitalok: ret
code ends
end start
由以上结果看出只有第一个单词转为大写了。
问题10.2
这个程序在思想上完全正确,但在细节上却有些错误,把错误找出来。
思考后看分析。
分析:问题在于cx的使用,主程序要使用cx记录循环次数,可是子程序中也使用了cx,在执行子程序
的时候,cx中保存的循环计数值被改变,使得主程序的循环出错。
从上面的问题中,实际上引出了一个一般化的问题:子程序中使用的寄存器,很可能在主程序中
也要使用,造成了寄存器使用上的冲突。
那么如何来避免这种冲突呢?粗略地看,可以有以下两个方案。
(1)在编写调用子程序的程序时,注意看看子程序中有没有用到会产生冲突的寄存器,
如果有,调用者使用别的寄存器;
(2)在编写子程序的时候,不要使用会产生冲突的寄存器。
我们来分析一下上面两个方案的可行性:
(1)这将给调用子程序的程序的编写造成很大的麻烦,因为必须要小心检查所有调用的子程序
中是否有将产生冲突的寄存器。比如说,在上面的例子中,我们在编写主程序的循环的时候就得检查
子程序中是否用到了bx和cx,因为如果子程序中用取了这两个寄存器就会出现问题。
如果采用这种方案来解决冲突的话,那么在主程序的循环中,就不能使用cx寄存器,因为子程序中已经用到。
(22)这个方案是不可能实现的,因为编写子程序的时候无法知道将来的调用情况。
可见,我们上面所设想的两个方案都不可行。我们希望:
(1)编写调用子程序的程序的时候不必关心子程序到底使用了哪些寄存器;
(2)编写子程序的时候不必关心调用者使用了哪些寄存器;
(3)不会发生寄存器冲突。
解决这个问题的简捷方法是,在子程序的开始将子程序中 所有用到的寄存器中的内容都保存起来,
在子程序返回前再恢复。可以用栈来保存寄存器中的内容。
以后,我们编写子程序的标准框架如下:
子程序开始:子程序中使用的寄存器入栈
子程序内容
子程序中使用的寄存器出栈
返回(ret,retf)
我们改进一下子程序capital的设计:
capital:push cx
push si
change: mov cl,[si]
mov ch,0
jcxz ok
and byte ptr [si],11011111b
inc si
jmp short change
ok: pop si
pop cx
ret
要注意寄存器入栈和出栈的顺序。
;t10_w2.asm
assume cs:code
data segmentdb 'word', 0db 'unix', 0db 'wind', 0db 'good', 0
data ends
code segment
start: mov ax, data mov ds, ax mov bx, 0mov cx, 4s: mov si, bx call capital add bx, 5 loop s mov ax, 4c00hint 21h capital:push cxpush sichange: mov cl, [si]mov ch, 0jcxz ok and byte ptr [si], 11011111b inc si jmp short change ok: pop sipop cxret
code ends
end start
debug调试:
。。。。。。
最终结果:
《汇编语言》第10章 call和ret指令相关推荐
- 汇编语言——第10章 CALL和RET指令
目录 引言 10.1 ret和retf 检测点10.1 10.2 call指令 10.3 依据位移进行转移的call指令 检测点10.2 10.4 转移的目的地址在指令中的call指令 检测点10.3 ...
- 王爽 汇编语言第三版 第10章 call 和 ret 指令 以及 子程序设计
第10章 call 和 ret 指令 10.1 ret 和 reft 指令 call 和 ret 指令都是转移指令,他们都修改 IP,或同事修改 CS 和 IP .他们经常被共同来实现子程序的设计. ...
- 汇编语言笔记(10)CALL和RET指令
call和ret指令都是转移指令,它们都修改IP,或者同时修改CS和IP ret和reft ret指令用栈中的数据,修改IP的内容,从而实现近转移: retf指令用栈中的数据,修改CS和IP的内容,从 ...
- 《汇编语言》第十章 call 和 ret 指令
call指令和ret指令都是转移指令,它们都修改IP,或同时修改CS和IP,它们经常被共同用来实现子程序的设计 10.1 ret 和 retf ret指令用栈中的数据,修改IP的内容,从而实现近转移 ...
- 汇编语言笔记10-CALL和RET指令
☞模块化程序设计 模块化程序设计 汇编语言通过call和ret指令实现了模块化程序设计.可以实现多个相互联系.功能独立的子程序来解决一个复杂的问题. 子程序的框架 1 assume cs:code 2 ...
- 汇编语言(第三版)第十章 CALL 和 RET 指令 笔记
call 和 ret 都是可以改变 ip 或是 cs 和 ip .经常用来实现子程序设计. 10.1 ret 和 retf ret指令用栈中的数据,修改IP实现近转移 retf指令用栈中的程序,修改c ...
- 王爽老师汇编语言第四版第十章CALL和RET指令——小白笔记
目录 10.1 ret和retf (1) ret: (2)retf: 10.2 call指令 10.3依据位移进行转移的CALL指令 10.4 转移的目的地址在指令中的CALL指令 10.5转移地址 ...
- (十)汇编语言——CALL和RET指令
(十)汇编语言--CALL和RET指令 文章目录 (十)汇编语言--CALL和RET指令 CALL指令 功能 寄存器 内存 段间转移 返回指令 ret retf 实例 MUL指令 模块化程序设计 寄存 ...
- 王爽 《汇编语言》 读书笔记 十 CALL和RET指令
第十章 CALL和RET指令 call和ret都是转移指令,它们都修改IP,或同时修改CS和IP,常用于子程序的设计. 10.1 ret 和 retf ret用栈中的数据,修改IP的内容,从而实现近转 ...
最新文章
- [转]VS2015编译的程序在其他机器上缺少msvcp120.dll
- Xcode7中创建静态库
- python第三方库有哪些-我常用的几个第三方 Python 库
- Kotlin极简教程:第10章 Kotlin与Java互操作
- 【luogu 3811】【模板】乘法逆元
- Linux监控软件之 Cacti
- 程序员面试题精选100题(27)-二元树的深度[数据结构]
- Linux下SSH远程连接断开后让程序继续运行解决办法
- java 引用被回收_java GC 静态List 如果没有引用会被回收吗
- html地址栏传值问题
- 计算机操作系统安装实验报告,操作系统实验报告1.doc
- Java 私塾面试系列
- 悉尼大学商业数据科学与计算机学院,悉尼大学数据科学专业
- Word入门教程之插入文字批注(转)
- java 反编译软件 推荐
- Xcode No account for team . Add a new account in the Accounts preference pane or verify
- jinkens搭建及部署项目
- close事件 vue_vue中v-on支持的事件总结
- 栈——栈的基本概念和基本操作
- 如何从VDS明网下载钱包