5.3.3  库测试程序

测试程序#1:整数I/O

该测试程序把输出文本的颜色改为蓝底黄字,然后以十六进制数显示七个数组的内容,最后提示用户输入一个有符号整数,再分别以十进制、十六进制和二进制格式重复显示该整数:

TITLE Library Test #1: Integer I/O (TestLib1.asm)

;Test the Clrscr,Crlf,DumpMem,ReadInt,

;SetTextColor,WaiMsg,WriteBin,WriteHex,

;and WriteString procedures.

INCLUDE Irvine32.inc

.data

arrayD DWORD 1000h,2000h,3000h

prompt1 BYTE "Enter a 32-bit stgned integer:",0

dwordVal DWORD ?

.code

start: call main

main PROC

mov eax ,yellow + (blue * 16)

call SetTextColor

call Clrscr

mov esi ,OFFSET arrayD

mov ecx ,LENGTHOF arrayD

mov ebx ,TYPE arrayD

call DumpMem

call Crlf

mov edx ,OFFSET prompt1

call WriteString

call ReadInt

mov dwordVal ,eax

call Crlf

call WriteInt

call Crlf

call WriteHex

call Crlf

call WriteBin

call Crlf

call WaitMsg

mov eax ,lightGray + (black * 16)

call SetTextColor

call Clrscr

exit

main ENDP

end start

运行结果:

测试程序#2:随机整数

第2个库测试程演示随机数使用过程。首先,随机产生10个在0~4294967294内的无符号整数,接着随机再生成10个在范围-50~+49内的有符号整数:

TITLE Link Library Test #2 (TestLib2.asm)

INCLUDE Irvine32.inc

TAB = 9

.code

main PROC

call  Randomize

call  Rand1

call  Rand2

exit

main ENDP

Rand1 PROC

mov  ecx ,10

L1: call  Random32

call  WriteDec

mov   al ,TAB

call  WriteChar

loop L1

call Crlf

ret

Rand1 ENDP

Rand2 PROC

mov  ecx ,10

L1 :mov  eax ,100

call RandomRange

sub  eax ,50

call WriteInt

mov  al ,TAB

call WriteChar

loop L1

call Crlf

ret

Rand2 ENDP

END main

运行结果:

测试程序#3:性能度量

汇编语言常用于优化对程序性能而言至关重要的代码。GetMseconds过程返回自午夜以来逝去的毫秒数,在循环之前调用了GetMseconds过程,然后执行嵌套循环约170亿次,在循环结束后再次调用GetMsgconds过程并报告用掉的时间:

TITLE Link Library Test #3

INCLUDE Irvine32.inc

OUTER_LOOP_COUNT = 3

.data

startTime DWORD ?

msg1 BYTE "Please wait..." ,0dh ,0ah ,0;

msg2 BYTE "Elapsed milliseconds:" ,0

.code

main PROC

mov  edx ,OFFSET msg1

call WriteString

call GetMSeconds

mov  startTime ,eax

mov  ecx ,OUTER_LOOP_COUNT

L1: call innerLoop

loop L1

call GetMSeconds

sub  eax ,startTime

mov  edx ,OFFSET msg2

call WriteString

call WriteDec

call Crlf

exit

main ENDP

innerLoop PROC

push ecx

mov  ecx ,0FFFFFFFFh

L1: mov  eax ,eax

loop L1

pop ecx

ret

innerLoop ENDP

END main


执行结果:

5.4  堆栈操作

堆栈的定义不解释了,后进先出。

5.4.1  运行时栈

运行时栈是由CPU直接管理的内存数组,它使用两个寄存器:SS和ESP。在保护模式下,SS寄存器存放的是段选择子,用户模式程序不应对其进行修改。ESP寄存器存放的是指向堆栈内特定位置的一个32位偏移值。我们很少需要直接操纵ESP的值,相反,ESP寄存器的值通常是由CALL,RET,PUSH和POP等指令间接修改的。

堆栈指令寄存器(ESP)指向最后压入(或添加)堆栈上的数据。

这里讨论的运行时栈同程序设计课程中讲述的堆栈抽象数据类型(stack ADT)是不同的。运行时栈在系统层上(由硬件直接实现)处理子过程调用;堆栈抽象数据类型通常用于实现依赖于先进后出操作的算法,一般使用高级语言如C++、Java等编写。

压栈操作

32位的压栈(PUSH)操作首先将堆栈指针减4,然后把要压栈的值赋值到堆栈指针所指向的位置处。

出栈操作

    出栈与压栈相反

堆栈中ESP之下的区域从逻辑上讲是空白的,在程序下次执行任何要压栈的指令时该区域将被覆盖重写。

堆栈的应用

寄存器在做多种用途的时候,堆栈可以方便的作为临时保存区域,在寄存器使用完毕之后,可通过堆栈恢复其原始值。

CALL指令执行的时候,CPU用堆栈保存当前被调用过程的返回地址。

调用过程的时候,可以通过压栈传递输入值(成为参数)。

过程内的局部变量在堆栈上创建,过程结束时,这些变量被丢弃。

5.4.2  PUSH和POP指令

PUSH指令

PUSH指令首先减少ESP的值,然后再把一个16位或32位的源操作数复制到堆栈上。对于16位的操作数,ESP值将减2;对于32位操作数,ESP值将减4.PUSH指令有一下三种格式:

PUSH  r/m16

PUSH  r/m32

PUSH  imm32

如果程序调用Irvine32中的过程,应总是压入32位值,否则库中使用的Win32控制数将不能正常运行。如果程序调用Irvine16中的库过程(实地址模式下)则可压入16位或32位的值。

在保护模式下立即数总是32位。在实地址模式下,如果未使用.386(或更高的)处理器伪指令,立即数默认是16位的。

POP指令

POP指令首先将ESP所指的堆栈元素复制到16位或32位的目的操作数中,然后增加ESP的值。如果操作数是16位的,ESP值将加2;如果操作数是32位的,ESP值将加4.其格式如下:

POP  r/m16

POP  r/m32

PUSHFD和POPFD指令

PUSHFD指令在堆栈上压入32位的EFLAGS寄存器的值,POPFD指令将堆栈顶部弹出并送至EFLAGS寄存器:

pushfd

popfd

实地址则是16位的。

MOV指令不能复制标志寄存器的值至变量或寄存器中,因此使用PUSHFD指令可能就是保存寄存器的最佳方式了。有时保存标志的备份以便后面进行恢复是很有用的,这通常可以是用PUSHFD和POPFD指令把一块指令包围起来:

pushfd              ;保存标志

;

;......

;

popfd               ;恢复标志

在使用这种类型的标志压栈和标志出栈指令的时候,必须确保程序的执行路径不会跳过POPFD指令。随着时间的推移,在修改程序时很难激情所有的压栈指令放在哪里。因此,编写准确的文档是非常关键的!

可以完成同样功能但或许可减少犯错误的方法是将标志保存在变量中:

.data

saveFlags DWORD ?

.code

pushfd                 ;标志入栈

pop saveFlags           ;复制到变量

下列语句从同一变量中回复标志值:

push saveFlags           ;将保存的标志入栈

popfd                  ;回复标志

PUSHAD,PUSHA,POPAD和POPA指令

PUSHAD指令在堆栈上按下列循序压入所有的32为通用寄存器:

EAX,ECX,EDX,EBX,RSP(执行PUSHAD指令之前的值),EBP,ESI和EDI;POPAD指令以相反的循序从堆栈中弹出这些通用寄存器。于此类似,80286处理器引入的PUSHA指令以括号中列表的循序压入所有的16位寄存器(AX,CX,DX,BX,SP,BP,SI和DI)。POPA指令则以相反顺序弹出这些寄存器。

如果在过程中修改了很多32位寄存器,那么可以在过程的开始和结束的位置分别用PUSHAD和POPAD指令保存和恢复寄存器的值。

MySub PROC

pushad

.

.

mov  eax ,...

mov  edx ,...

mov  ecx ,...

.

.

popad

ret

MySub ENDP

对上面的例子,有一个例外情况必须指出:过程通过一个或多个寄存器返回结果时不应该使用PUSHA或PUSHAD指令。假设下面的RcadValue过程想要通过EAX返回一个整数但对POPAD的调用将覆盖EAX中的返回值:

ReadValue PROC

pushad              ;保存通用寄存器

.

.

mov  eax ,return_value

.

.

popad

ret

ReadValue ENDP

例子:反转字符串

RevStr.asm程序循环遍历字符串并把每个字符串都压入堆栈,然后取出来。

TITLE Reversing a String  (RevStr.asm)

INCLUDE  Irvine32.inc

.data

aName BYTE "Abraham Lincoln",0

nameSize = ($ - aName) - 1

.code

main PROC

mov  ecx ,nameSize

mov  esi ,0

mov  eax ,0

L1: mov  al,aName[esi]

push eax

inc  esi

loop L1

mov  ecx ,nameSize

mov  esi ,0

L2: pop  eax

mov  aName[esi] ,al

inc  esi

loop L2

mov  edx ,OFFSET aName

call WriteString

call Crlf

exit

main ENDP

END main

运行结果:

TIP:书中上面的代码有第一个地方有问题(L1: mov eax,aName[esi]),如果这么写编译器会弹出编译错误,原因是aName[esi]是8位,eax是32位的,但是eax是32位,ax是第16位,al是第八位,所以直接eax换成al就行了,但是记住之前要把eax清零,因为堆栈是接收32位的,我们处理al之后把eax压入堆栈可能把前面的高位压进去(但是上面这个程序压进去结果也看不出来,因为我们始终只操作al),但是压入eax进栈本身就是错误的。所以需要eax清零。在使用低位。

5.5  过程的定义和使用

可以理解成是C++或是其它语言里的函数或者方法等。

5.5.1  PROC伪指令

过程的定义

可以把过程非正式地定义为以返回语句结束的命令语句块。过程使用PROC伪指令和ENDP伪指令来声明,另外还必须给过程定义一个名字。到现在写的所有程序都包含一个名为main的过程,例如:

main PROC

.

.

.

main ENDP

程序启动过程之外的其他过程以RET指令结束,以强制CPU返回到过程被调用的地方:

sample  PROC

.

.

ret

sample  ENDP

启动过程(main)是个特例,它以exit语句结束。如果程序中使用了INCLUD Irvine32.inc语句的话,exit语句实际上就是对ExitProcess函数的调用,ExitProcess是用来终止程序的系统函数

INVOKE ExitProcess ,0

如果在程序中使用了INCLUDE Irvine16.inc语句,那么exit被翻译成.EXIT伪指令。汇编器为.EXIT生成下面两条语句:

mov  ah,4C00h   ;调用MS-DOS的4c00h功能

int 21h          ;终止程序

例子:三个整数之和

我们创建一个名为SumOf的过程来计算3个32位整数之和,假设合适的整数在过程被调用以前已经存放在EAX、EBX和ECX寄存器中了,过程在EAX中发回和:

SumOf PROC

add  eax ,ebx

add  eax ,ecx

ret

SumOf ENDP

为过程添加文档

应该养成的良好编程习惯之一就是为程序添加清晰易读的文档。下面是对放在每个过程开始处的文档信息的几点建议:

过程完成的所有任务的描述。

输入参数的清单使用方法。

过程返回值的描述。

列出特殊要求。

;------------------------------------------------------------

SumOf PROC

;

;Calculates and returns the sum of three 32-bit integers.

;Receines:EAX,EBX,ECX,the three integers,May be signed or unsigned.

;Retuens: EAX = sum

;--------------------------------------------------------------

add  eax ,ebx

add  eax ,ecx

ret

SumOf ENDP

用C/C++之类的高级语言编写的函数,典型情况下在AL中返回8位值,在AX中返回16位置,在EAX中返回32位值。

5.5.2  CALL和RET指令

CALL指令只是处理器在新的内存地址执行指令,以实现过程的调用。过程使用RET(从过程返回)指令使处理器返回到程序过程被调用的地方继续执行。从底层细节角度来讲,CALL指令把返回地址压入堆栈并把被调用过程的地址复制到指令寄存器中。当程序返回时,RET指令从堆栈中弹出返回地址并送到指令寄存器中。在32位模式下,CPU总是执行EIP(指令指针寄存器)所指向的内存出的指令;在16位模式下,CPU总是执行IP寄存器指向的指令。

调用和返回的例子

假设在main中,CALL语句位于偏移00000020处。通常CALL指令的机器码需要5字节,因此下一条指令位于偏移00000025处:

main PROC

00000020      call  MySub

00000025      mov eax ,ebx

接下来,假设MySub中的第一条指令位于偏移00000040处:

MySub PROC

00000040    mov  eax ,edx

.

.

ret

MySub ENDP

当CALL指令执行的时候,金针CALL指令的地址(00000025)被压入堆栈,而MySub的地址被装入EIP。MySub内的指令开始执行,一直到RET指令位置。当RET指令被执行的时候,ESP所指的堆栈值被弹出并送至EIP。第二部,ESP的值将减少以指向堆栈上的前一个值。

Sub3过程结束的时候执行RET指令,从堆栈中弹出[ESP]处的值送至指令寄存器,这将使得CPU从紧跟调用Sub3之后的指令处恢复执行,下图显示了在从Sub3过程返回之前的堆栈状况:

返回之后,ESP指向相邻的堆栈表项,在Sub2末尾RET指令准备执行时,堆栈如下表示:

TIP:看到这我就一直在想一个问题,如果我自己写了一个函数,然后我在里面直接更改了堆栈,但是我并没有还原相关,也就是我更改堆栈会不会导致这个函数return不回去(因为我不确定我用的堆栈和CPU调度用的堆栈是不是同一个堆栈,也就是作用域的问题),于是我做了这个尝试:

TITLE TEST STACK (teststack.asm)

INCLUDE Irvine32.inc

.data

strStart BYTE "Start!" ,0dh ,0ah,0

strEnd   BYTE "End!" ,0dh ,0ah ,0

strTest  BYTE "RunTestFun!",0dh ,0ah ,0

.code

main PROC

mov  edx ,OFFSET strStart

call WriteString

call TestFun

mov  edx ,OFFSET strEnd

call WriteString

exit

main ENDP

TESTFun PROC

mov edx ,OFFSET strTest

call WriteString

pop edx

ret

TESTFun ENDP

END main

我在函数里直接POP了栈里的东西,如果用的是同一个栈,那么这样应该是跳转不回去的。结果也应该是不可预知的。然后操作的结果却是是这样。直接没有return成功,我还用vs反汇编看了下地址,在函数里面POP出来的那个值就是call
TestFun接下来那个call writestring的地址。so...

过程的嵌套调用

被调用的过程在返回之前又调用了其他过程时,就发生了过程嵌套调用。假设main调用了过程Sub1,Sub1执行的时候又调用了过程Sub2,Sub2执行的时候又调用了Sub3,这个过程如下图:

最后,当Sub1返回时,堆栈中的[ESP]被弹出送指令指针寄存器,CPU在main中回复继续执行:

显然,堆栈已经被证明是存储信息(如嵌套过程调用的相关信息)的有效工具。通常堆栈适用于程序要以特定顺序回溯执行某些步骤的情况。

向过程传递参数

如果想要编写一个执行某些标准操作的过程,如计算整理数组之和的过程,那么在过程之内引用特定的变量并不是什么好主意。如果那么做的话,该过程就不可能用于其他数组了。一个较好的办法就是向过程传递参数。在汇编语言中,通过通用寄存器传递参数的做法是很普遍的。

上节中我们编写了一个把EAX,EBX和ECX寄存器中整数相加的过程SumOf。在main中调用SumOf之前,首先为EAX,EBX和ECX寄存器赋值:

data

theSum  DWORD ?

.code

main PROC

mov eax ,10000h

mov ebx ,20000h

mov ecx ,30000h

call SumOf

mov theSum ,eax

在CALL语句之后,可以把EAX中的和复制到一个变量中保存。

5.5.3  例子:对整数数组求和

一种非常常见的类型的循环是计算整数数组之和,或许读者用C++或Java编写过,在汇编语言中是非常易于实现的,经过精心编写,循环可以以尽可能快的速度运行。比如我们可以在循环中使用寄存器而不是变量。

下面创建一个名为AraySum的过程,它从调用程序那里接受连个参数:一个指向32位整数数组的指针和一个包含数组元素数目的技术,ArraySum计算数组之和并通过EAX寄存器返回:

;----------------------------------

ArraySum PROC

;

;calculates the sum of array of 32-bit integers.

;Receives: ESI = the array offset

;          ECX = number of elements in the array

;Returns : EAX = sum of the array elements

;-----------------------------------

push  esi

push  ecx

mov   eax ,0

L1:

add   eax ,[esi]

add   esi ,TYPE DWORD

loop  L1

pop  ecx

pop  esi

ret

ArraySum  ENDP

注意该过程中没有任何东西与特定数组的名字或大小相关,所以它可用于任何需要计算32位整数数组和的程序。无论何时只要有可能的话,读者应尽量编写灵活和易于修改的过程。

调用ArraySum:下面是一个调用ArraySum的过程的例子,通过ESI传递array的地址,并通过ECX传递数组元素数目。在调用之后,把EAX中的和复制到一个变量中。

INCLUDE Irvine32.inc

.data

array  DWORD 10000h ,20000h ,30000h ,40000h ,50000h

theSum DWORD ?

.code

main PROC

mov esi ,OFFSET array

mov ecx ,LENGTHOF array

call ArraySum

mov theSum ,eax

main ENDP

5.5.4  流程图

流程图是以图形化的方式描述程序逻辑的有效方法。流程图中的每个图形都表示一个逻辑步骤,把图形连接起来的带箭头的先显示了逻辑步骤之间的次序:

来一个ArraySum过程设计一个简单的流程图。

5.5.5  保存和恢复寄存器

读者已经可能注意到在ArraySum过程的开始处ECX和ESI被压入堆栈,过程结束的时候又被弹出,绝大多数修改寄存器值的过程都使用这种方式。修改寄存器值的过程应该总是保存和恢复寄存器值,以确保调用程序本身的寄存器值不会覆盖改写。这个规则的一种例外情况是用寄存器发回结果时,这时不要对这个寄存器进行保存和恢复工作。

USER操作符

与PROC伪指令配套使用的USER操作符允许列出被过程修改的所有寄存器,它只是编译器做两件事:首先,在过程开始处生成PUSH指令在堆栈上保存寄存器;其次,在过程结束的处生成POP指令恢复这些寄存器的值。USER操作符应该紧跟PROC伪指令,其后跟由空格和制表符分割的寄存器列表。

5.5.3节中的ArraySum过程使用PUSH和POP指令保存和恢复被过程修改的寄存器ESI和ECX。使用USER操作符做相同的事情更简单一些:

ArraySum PROC USER esi ,ecx

mov  eax ,0

L1:

add  eax ,[esi]

add  esi ,4

loop L1

ret

ArraySum ENDP

汇编生成的相应代码显示了使用USER操作符的效果:

Array PROC

push  esi

push  ecx

mov   eax ,0

L1:

add  eax ,[esi]

add  esi ,4

loop  L1

pop  ecx

pop  esi

ret

Array ENDP

5.6.1  整数求和程序(设计)

写一个程序,提示用户输入3个32位整数,将其保存在数组中,计算数组内的元素的和并在屏幕上显示。

TITLE Integer Summation Program  (Sum2.asm)

; This program prompts the user for three integers,

; stores them in an array, calculates the sum of the

; array ,and displays the sum.

INCLUDE Irvine32.inc

INTEGER_COUNT = 3

.data

str1  BYTE  "Enter a signed integer:" ,0

str2  BYTE  "The sum of the integers is:" ,0

array DWORD INTEGER_COUNT DUP(?)

.code

main PROC

call Clrscr

mov  esi ,OFFSET array

mov  ecx ,INTEGER_COUNT

call PromptForIntegers

call ArraySum

call DisplaySum

exit

main ENDP

;-----------------------------------------

PromptForIntegers PROC USES ecx edx esi

;

; Prompts the user for an arbitrary number of integers

; and inserts the integers into an array.

; Receives: ESI points to the array ,ECX = array size

; Return: nothing

;-----------------------------------------

mov  edx ,OFFSET str1

L1: call WriteString

call ReadInt

call Crlf

mov  [esi] ,eax

add  esi ,TYPE DWORD

loop L1

ret

PromptForIntegers ENDP

;----------------------------------------

ArraySum PROC USES esi ecx

;

; Calculates the sum of an array of 32-bit integers.

; Receives : ESI points to the array, ECX = number

;  of array elements

; Returns: EAX = sum of the array elements

;------------------------------------------

mov  eax ,0

L1: add  eax ,[esi]

add  esi ,TYPE DWORD

loop L1

ret

ArraySum ENDP

;------------------------------------------

DisplaySum PROC USES edx

;

; Displays the sum on the screen

; Receives :EAX = the sum

; Returns  nothing

;-------------------------------------------

mov  edx ,OFFSET str2

call WriteString

call WriteInt

call Crlf

ret

DisplaySum ENDP

END main

运行结果:

 

5.7  本章小结

Intel汇编语言程序设计学习-第五章 过程-下的更多相关文章

  1. Intel汇编语言程序设计学习-第五章 过程-上

    过程 5.1  简介 需要阅读本章的理由可能很多: 1.读者可能想要学习如何在汇编语言中进行输入输出. 2.应该了解运行时栈(runtime stack),运行时栈是子过程(函数)调用以及从子过程返回 ...

  2. Intel汇编语言程序设计学习-第六章 条件处理-下

    6.6  应用:有限状态机 这个东西说了半天,感觉就是把逻辑弄得跟有向图一样,没看出来什么高端的东西,下面就整理下书上说的概念: 有限状态机(FSM,Finite-State Machine)是依据输 ...

  3. Intel汇编语言程序设计学习-第四章 数据传送、寻址和算术运算-下

    4.3  和数据相关的操作符和伪指令 操作符和伪指令并非机器可执行的指令,相反,它们是由汇编器进行解释的.开发者可以使用一系列的MASM操作符或伪指令获取数据的地址以及大小等特征信息: OFFSET操 ...

  4. Intel汇编语言程序设计学习-第三章 汇编语言基础-下

    3.4  定义数据 3.4.1  内部数据类型 MASM定义了多种内部数据类型,每种数据类型都描述了该模型的变量和表达式的取值集合.数据类型的基本特征是以数据位的数目量的大小:8,16,32,,48, ...

  5. Intel汇编语言程序设计学习-第六章 条件处理-上

    条件处理 本章要点 1.简介 2.布尔和比较指令 3.条件跳转 4.条件循环指令 5.条件结构 6.应用:有限状态机 7.决策伪指令 6.1  简介 本章,读者将看到高级条件分支如何翻译成底层的实现代 ...

  6. Intel汇编语言程序设计学习-第三章 汇编语言基础-中

    3.2  例子:整数相加减 现在来看一个进行整数加减操作的汇编语言小程序.寄存器用于存放中间数据,我们调用一个库函数在屏幕上显示寄存器的内容.下面是程序的源码: TITLE Add and Subtr ...

  7. Intel汇编语言程序设计学习-第三章 汇编语言基础-上

    汇编语言基础 3.1  汇编语言的基本元素 有人说汇编难,有人说汇编简单,我个人不做评价,下面是一个简单的实例(部分代码): main PROC mov  eax,5  ;5送EAX寄存器 add   ...

  8. Intel汇编语言程序设计学习-第六章 条件处理-中

    6.3  条件跳转 6.3.1  条件结构 在IA-32指令集中没有高级的逻辑结构,但无论多么复杂的结构,都可以使用比较和跳转指令组合来实现.执行条件语句包括两个步骤:首先,使用CMP,AND,SUB ...

  9. Intel汇编语言程序设计学习-第一章 基本概念

    第一章基本概念 1.1  简单介绍 本书着重讲述MS-Windows平台上IA-32(Intel Architecture 32bit,英特尔32位体系架构)兼容微处理器的汇编语言程序设计,可以使用I ...

随机推荐

  1. CVE-2016-5734-phpmyadmin-4.0.x-4.6.2-代码执行

    参考 https://www.jianshu.com/p/8e44cb1b5b5b 漏洞原因 phpMyAdmin是一套开源的.基于Web的MySQL数据库管理工具.在其查找并替换字符串功能中,将用户 ...

  2. 《Asp.Net Core3 + Vue3入坑教程》 - Vue 1.使用vue-cli创建vue项目

    简介 <Asp.Net Core3 + Vue3入坑教程> 此教程适合新手入门或者前后端分离尝试者.可以根据图文一步一步进操作编码也可以选择直接查看源码.每一篇文章都有对应的源码 目录 & ...

  3. ZooKeeper 的选举机制,你了解多少?

    本文作者:HelloGitHub-老荀 Hi,这里是 HelloGitHub 推出的 HelloZooKeeper 系列,免费开源.有趣.入门级的 ZooKeeper 教程,面向有编程基础的新手. 项 ...

  4. 【译】Rust宏:教程与示例(二)

    原文标题:Macros in Rust: A tutorial with examples 原文链接:https://blog.logrocket.com/macros-in-rust-a-tutor ...

  5. java常见面试题3:线程间通信

    写两个线程,一个线程打印 1~52,另一个线程打印字母A-Z. 打印顺序为12A34B56C78D--5152Z.要求用线程间的通信. 代码清单: class Printer { private in ...

  6. Activity类组成分析(一)Instrumentation

    目录 前言 解剖 继承关系 重要成员 Instrumentation 总结 前言 要了解清楚StartActivity的过程,Activity对象实例的构造过程是重要组成部分:而要弄清楚Activit ...

  7. 一文读懂MySql主从复制机制

    作为一个关系型数据库,MySQL内建地提供数据复制机制,这使得在使用时,可以基于其复制机制实现高可用架构等高级特性,从而使得MySQL无需借助额外的插件或其他工具就具备适用于生产环境.这是MySQL得 ...

  8. 基于ZXing.Net生成一维二维码

    新阁教育-喜科堂付工原创 最近很多小伙伴对一维码.二维码比较感兴趣,今天主要给大家分享一个C#生成条形码和二维码的案例. C#作为一个高级语言,特点就是快! 我们使用的是开源库ZXing,ZXing是 ...

  9. css盒模型以及如何计算盒子的宽度

    css盒模型以及如何计算盒子的宽度 盒模型 每个存在于可访问性树中的元素都会被浏览器绘制成一个盒子[1]. 每个盒子都可以看成由4部分组成,它们分别是 - 元素外边距(margin).元素边框(bor ...

  10. BUAA_2020_OO_UNIT4_REVIEW&ALL_REVIEW

    OO第四单元总结&&学期总结 1. 第四单元作业总结 本单元三次作业都围绕了UML图的建模展开,第十三次作业只有类图,第十四次作业增加了顺序图和状态图,第十五次增加了部分UML规则的判 ...