六、汇编实战

news/2024/12/27 18:42:35/文章来源:https://www.cnblogs.com/liuhousheng/p/18636541

打印:hello world

在屏幕上输出字符

mov    dl,'a'			 ; 将要打印的字符放到dl中
mov    ah, 02h           ; 设置显示字符的功能号
int    21h               ; 调用DOS中断,打印字符

在屏幕上输出字符串

mov    ah,09h              ;设置显示字符串的功能号
int    21H                 ;调用BIOS中断

字符串的结束符:$

在一个段中定义要打印的字符串,并在字符串结尾写上'$',当输出字符的中断遇到"$"时,就会停止打印字符

data segmentstr  db 'Hello World', 0dh, 0ah, '$'    ;在data段定义要打印的字符串;其中0dh和0ah为回车和换行符;'$'为字符串结束符
data ends

打印字符串

data segmentstr  db 'Hello World', 0dh, 0ah, '$'    ;在data段定义要打印的字符串;其中0dh和0ah为回车和换行符;'$'为字符串结束符
data endscode segment;关联code段和data段的段地址寄存器assume cs:code, ds:datastart:;关联data段mov    ax,datamov    ds,axmov    dx,offset str       ;设置dx指向str字符串的起始位置mov    ah,09h              ;设置显示字符串的功能号int    21H                 ;调用BIOS中断;退出程序mov    ah,4CHint    21H
code ends
end start

运行后效果如下:

image

打印uint16_t范围的数字

C语言版

注意:Uart0_PutChar的作用是串口发送一个字节的数据,类似于打印一个字符串的功能

/*** 函    数:次方函数(内部使用)* 返 回 值:返回值等于X的Y次方*/
uint16_t Serial_Pow(uint16_t X, uint16_t Y)
{uint16_t Result = 1;	//设置结果初值为1while (Y--)			//执行Y次{Result *= X;		//将X累乘到结果}return Result;
}/*** 函    数:串口发送数字* 参    数:Number 要发送的数字,范围:0~65535* 参    数:Length 要发送数字的长度,范围:0~5* 返 回 值:无*/
void print_uint16_t(uint16_t Number, uint8_t Length)
{uint8_t i;for (i = 0; i < Length; i++)		//根据数字长度遍历数字的每一位{Uart0_PutChar(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');	//依次调用Uart0_PutChar发送每位数字}
}

转换为汇编

data segment;定义数据空间str    db 5 dup(0), 0dh, 0ah,'$'      ;用于在屏幕上显示的字符串var    dw 0                           ;在程序运行时可供使用的变量Len    dw 0                           ;待打印的数字长度Number dw 0                           ;待打印的数字Result dw 0                           ;数字取整所要除的数
data endsstack segment;定义栈空间dw 10  dup(0)
stack endscode segment;关联code段和data段的段地址寄存器assume cs:code, ds:data, ss:stack;定义可以在屏幕上打印uint16范围内的数字的函数print_uint16:;先清空str中的数据mov    si,offset strmov    cx,5mov    ax,32Clear:       mov    [si],ax                         ;32对应的ascll是空格inc    siloop   Clearmov    cx,[Len]                        ;要打印的数字长度s:           push   cx                              ;保存循环计数器dec    cxmov    [Result], 1                     ;初始化除数;次方函数Serial_Pow:  cmp    cx,0je     s2mov    ax,10                           ;计算16位的乘法,其中一个乘数要在ax中mul    [Result]                        ;计算Result乘以10mov    [Result],ax                     ;16位的乘法低16位在ax中,将其移到Result中,因为Result不会超过16位,所以不用管高16位loop   Serial_Pows2:          pop    cx                              ;恢复循环计数器;计算16位的除法,被除数必须放在在dx和ax中,除数在Result中mov    dx,0mov    ax,[Number]                     ;将32位被除数放入ax和bx中div    [Result]                        ;对Number取整,取整的结果在ax中,余数在dx中;ax中存放的是取整后的结果,将dx清零后可再次进行除法运算mov    dx,0                            ;清除余数,准备对取整后的结果再进行取余mov    bx,10div    bx                              ;对十进制数取余,余数在dx中add    dl,30h                          ;将余数转换为ASCII码;将数字高位放到左边,低位放到右边mov    ax,[Len]mov    [var],axsub    [var],cxmov    bx,[var]mov    [str+bx],dlloop   s;打印字符串mov    dx,offset str                   ;设置显示字符串的偏移地址mov    ah,09h                          ;设置显示字符串的功能号int    21H                             ;调用BIOS中断ret;程序开始,相当于main函数start:       ;关联段与段寄存器mov    ax,datamov    ds,axmov    ax,stackmov    ss,ax;相当于给print_uint16函数传参mov    [Number],12345                  ;在屏幕上打印12345mov    [Len],5;调用print_uint16函数call   print_uint16mov    [Number],65535                  ;在屏幕上打印65535mov    [Len],5call   print_uint16mov    [Number],777                    ;在屏幕上打印777mov    [Len],3call   print_uint16mov    [Number],1                      ;在屏幕上打印00001mov    [Len],5call   print_uint16;退出程序mov    ah,4CHint    21H
code ends
end start

运行后效果如下:

image

优化版

该版本不用设置数字长度,只需要将要打印的uint16_t类型的数字放到Number中即可

先看分文件编写后再看这个

; 声明该代码块可以被其他模块访问和调用
public Lib_print_uint16.model small                ; 使用小内存模型,代码段和数据段各64KB
.stack 100h                 ; 定义堆栈段大小为256字节 (100h = 256).dataNumber dw 0             ; 定义一个字(16位)变量Number,初始值为0Result dw 0             ; 定义一个字(16位)变量Result,用于存储数字取整所要除的数var    dw 0             ; 定义一个字(16位)变量var,用于在程序运行时临时存储数据.codeLib_print_uint16 proc; 从栈中获取Number的地址pop    [var]            ; 获取返回地址到var中,因为是近距离转移,所以只把偏移地址压入栈中,pop一次就够了pop    [Number]         ; 将要打印的数字放到Number中push   [var]            ; 将返回地址重新压入栈中; 保存寄存器状态push   axpush   bxpush   cxpush   dxpush   sipush   dimov    bx, 10            ; 计算十进制的位数,用10对待计算的数取整mov    cx, 0             ; 初始化cx寄存器为0,cx将用于计数Number的位数mov    ax, [Number]      ; 将Number的值加载到ax寄存器中cmp    ax, 0             ; 比较ax寄存器中的值是否为0jne    Count_Bits        ; 如果ax不为0,跳转到Count_Bits标签,开始计数位数; 如果ax为0,直接打印并退出mov    dl, 30h           ; 将字符'0'加载到dl寄存器mov    ah, 2             ; 设置ah为2,这是DOS中断21h中用于输出字符的服务号int    21h               ; 调用DOS中断21h,输出字符'0'mov    dl, 0Ah           ; 将0Ah(换行符)加载到dl寄存器mov    ah, 2             ; 设置ah为2,这是DOS中断21h中用于输出字符的服务号int    21h               ; 调用DOS中断21h,输出换行符mov    dl, 0Dh           ; 将0Dh(回车符)加载到dl寄存器mov    ah, 2             ; 设置ah为2,这是DOS中断21h中用于输出字符的服务号int    21h               ; 调用DOS中断21h,输出回车符jmp    End_Count         ; 跳转到End_Count标签,打印Count_Bits:inc    cx                ; 增加cx寄存器的值,计数器加1mov    dx, 0             ; 清空dx寄存器,用于除法操作div    bx                ; 将ax寄存器中的值除以bx寄存器中的值,ax = ax / bx, dx = ax % bxcmp    ax, 0             ; 比较ax寄存器中的值是否为0jne    Count_Bits        ; 如果ax不为0,继续循环; 开始循环打印数字,数字长度已经在cx中
s:push   cx                ; 保存循环计数器dec    cx                ; 将cx减1,用于计算当前位数mov    [Result], 1       ; 初始化除数Result为1
Serial_Pow:cmp    cx, 0             ; 比较cx和0je     s2                ; 如果cx等于0,跳转到s2mov    ax, 10            ; 将10加载到ax寄存器mul    [Result]          ; 计算Result乘以10,结果低16位在ax,高16位在dxmov    [Result], ax      ; 将ax中的结果存储到Result中loop   Serial_Pow        ; 循环直到cx为0,计算10的幂次
s2:pop    cx                ; 恢复循环计数器; 计算16位的除法,被除数必须放在dx和ax中,除数在Result中mov    dx, 0             ; 清空dx寄存器,准备进行除法运算mov    ax, [Number]      ; 将Number的值加载到ax寄存器div    [Result]          ; 对Number取整,取整的结果在ax中,余数在dx中; ax中存放的是取整后的结果,将dx清零后可再次进行除法运算mov    dx, 0             ; 清空dx寄存器,准备进行下一步的除法运算div    bx                ; 对十进制数取余,余数在dx中add    dl, 30h           ; 将余数转换为ASCII码(数字0的ASCII码是30h)mov    ah, 02h           ; 设置显示字符的功能号int    21h               ; 调用DOS中断,打印字符loop   s                 ; 循环直到cx为0,将数字转换为字符串mov    dl, 0Ah           ; 将0Ah(换行符)加载到dl寄存器mov    ah, 2             ; 设置ah为2,这是DOS中断21h中用于输出字符的服务号int    21h               ; 调用DOS中断21h,输出换行符mov    dl, 0Dh           ; 将0Dh(回车符)加载到dl寄存器mov    ah, 2             ; 设置ah为2,这是DOS中断21h中用于输出字符的服务号int    21h               ; 调用DOS中断21h,输出回车符End_Count:; 恢复寄存器的状态pop    di                ; 恢复di寄存器pop    si                ; 恢复si寄存器pop    dx                ; 恢复dx寄存器pop    cx                ; 恢复cx寄存器pop    bx                ; 恢复bx寄存器pop    ax                ; 恢复ax寄存器ret                      ; 返回调用处
Lib_print_uint16 endp        ; 结束过程定义end                          ; 标记程序结束

再次优化版

该版本可以指定进制

对应的c代码:

/*
* 函    数:将uint16_t范围内的整数,转换成任意进制的字符串,并打印
* 参    数:Number 待打印的整数,范围:0~65535
* 参    数:scale 指定进制,范围:0~16
* 返 回 值:无
*/
void print_uint16_t(unsigned int Number, int Scale)
{char str[16];int count = 0;while (Number){char b = Number % Scale;if ((b += 0x30) > 57)b += 7;str[count] = b;Number /= Scale;count++;}while (count){count--;printf("%c", str[count]);}printf("\r\n");
}
; 声明该代码块可以被其他模块访问和调用
public Lib_print_uint16.model small                 ; 使用小内存模型,代码段和数据段各64KB
.stack 100h                  ; 定义堆栈段大小为256字节 (100h = 256).datastr    db 16 dup(0)      ; 存储待打印的字符,初始化为16个0字节的空间scale  dw 0              ; 进制,这里用于设置要转换的数字的进制,默认为0Number dw 0              ; 定义一个字(16位)变量Number,初始值为0,用于存放要打印的数字.code                        ; 代码段开始Lib_print_uint16 proc        ; 定义一个名为Lib_print_uint16的过程; 从栈中获取Number的地址pop    word ptr [str]    ; 将返回地址暂时存在str的前两个字中pop    [Number]          ; 将栈顶的值弹出并存储到Number变量中,这个值是要打印的16位无符号整数pop    [scale]           ; 将栈顶的值弹出并存储到scale变量中,这个值是要转换的进制push   word ptr [str]    ; 将之前弹出的返回地址重新压入栈中,以便正确返回调用点; 保存寄存器状态,防止过程执行期间修改寄存器值影响到调用方push   ax                ; 保存ax寄存器的值push   bx                ; 保存bx寄存器的值push   cx                ; 保存cx寄存器的值push   dx                ; 保存dx寄存器的值push   si                ; 保存si寄存器的值push   di                ; 保存di寄存器的值mov    ax, [Number]      ; 将Number的值加载到ax寄存器中,准备进行除法操作mov    bx, [scale]       ; 将scale的值加载到bx寄存器中,这里应该是要转换的进制数(如10进制)mov    si, 0             ; 初始化si为0,作为索引,用于向str中存储字符mov    dx, 0             ; 清空dx寄存器,用于除法操作前的准备。在div指令中,dx:ax会作为被除数
for1:mov    dx, 0             ; 在每次循环开始前,清空dx,确保除法操作的正确性div    bx                ; 用ax除以bx(scale),ax存放商,dx存放余数。这里将Number的值转换为字符表示add    dl, 30h           ; 将余数转换为ASCII码,30h是字符'0'的ASCII码,加30h后可以得到相应的数字字符cmp    dl, 58            ; 检查dl中的ASCII码是否大于'9'(58h是字符':'的ASCII码,但在这里应该是想检查是否大于'9')jb     No_16             ; 如果dl中的ASCII码不大于'9',跳转到No_16标签处add    dl, 7             ; 如果dl中的ASCII码大于'9',说明是10进制以上的转换,这里将dl加7h是想将字符转换为'A'-'F'(在16进制中)
No_16:mov    str[si], dl       ; 将转换后的字符存储到str数组中,si作为索引inc    si                ; si索引加1,指向数组的下一个位置cmp    ax, 0             ; 比较商ax是否为0jne    for1              ; 如果商ax不为0,继续循环for2:dec    si                ; 循环结束后,si指向的是数组的结束位置(多加了一次1),所以这里si减1指向数组的最后一个元素mov    dl, str[si]       ; 将str数组中si位置的字符加载到dl寄存器中,准备输出mov    ah, 2             ; 设置ah为2,这是DOS中断21h中用于输出字符的服务号int    21h               ; 调用DOS中断21h,输出dl中的字符cmp    si, 0             ; 比较si是否为0jne    for2              ; 如果si不为0,继续循环,输出str数组中的所有字符mov    dl, 0Ah           ; 将0Ah(换行符)加载到dl寄存器中,准备输出换行符mov    ah, 2             ; 设置ah为2,这是DOS中断21h中用于输出字符的服务号int    21h               ; 调用DOS中断21h,输出换行符mov    dl, 0Dh           ; 将0Dh(回车符)加载到dl寄存器中,准备输出回车符mov    ah, 2             ; 设置ah为2,这是DOS中断21h中用于输出字符的服务号int    21h               ; 调用DOS中断21h,输出回车符; 恢复寄存器的状态,确保调用方的寄存器未被修改pop    di                ; 恢复di寄存器的值pop    si                ; 恢复si寄存器的值pop    dx                ; 恢复dx寄存器的值pop    cx                ; 恢复cx寄存器的值pop    bx                ; 恢复bx寄存器的值pop    ax                ; 恢复ax寄存器的值ret                      ; 返回调用处Lib_print_uint16 endp        ; 结束过程定义end                          ; 标记程序结束

分文件编写

内存模型

在MASM(Microsoft Macro Assembler)中,内存模型(memory model)定义了程序中代码段(code segment)、数据段(data segment)、堆栈段(stack segment)和附加段(extra segment)的大小和访问方式。

不同的内存模型适用于不同大小的程序和不同的内存管理需求。

  1. 紧凑型内存模型(.model compact)

    • 段的大小

      • 代码段(CS): 64KB
      • 数据段(DS): 64KB
      • 堆栈段(SS): 64KB
      • 附加段(ES): 64KB
    • 特点

      • 代码段和数据段可以共享同一个64KB的地址空间。
      • 堆栈段和附加段可以共享同一个64KB的地址空间。
      • 使用近指针(near pointers)和远指针(far pointers)。
      • 适用于需要在数据段和堆栈段之间共享内存的程序。
  2. 中型内存模型(.model medium)

    • 段的大小

      • 代码段(CS): 1MB
      • 数据段(DS): 64KB
      • 堆栈段(SS): 64KB
      • 附加段(ES): 64KB
    • 特点

      • 代码段可以超过64KB,达到1MB。
      • 数据段、堆栈段和附加段每个都限制在64KB的范围内。
      • 使用远指针(far pointers)。
      • 适用于代码量较大但数据量较小的程序。
  3. 大型内存模型(.model large)

    • 段的大小

      • 代码段(CS): 1MB
      • 数据段(DS): 1MB
      • 堆栈段(SS): 1MB
      • 附加段(ES): 1MB
    • 特点

      • 所有段都可以达到1MB的大小。
      • 使用远指针(far pointers)。
      • 适用于大型程序,需要更大的代码和数据段。
      • 段寄存器(CS、DS、SS、ES)可以独立指向不同的段。
  4. 巨大内存模型(.model huge)

    • 段的大小

      • 代码段(CS): 1MB
      • 数据段(DS): 1MB
      • 堆栈段(SS): 1MB
      • 附加段(ES): 1MB
    • 特点

      • 类似于大型内存模型,但主要用于处理非常大的数据结构。
      • 使用远指针(far pointers)。
      • 适用于需要处理非常大的数据结构的程序。
  5. 大型紧凑型内存模型(.model large compact)

    • 段的大小

      • 代码段(CS): 1MB
      • 数据段(DS): 64KB
      • 堆栈段(SS): 64KB
      • 附加段(ES): 64KB
    • 特点

      • 代码段可以达到1MB。
      • 数据段、堆栈段和附加段每个都限制在64KB的范围内。
      • 使用远指针(far pointers)。
      • 适用于代码量较大但数据量较小的程序,数据段和堆栈段可以共享64KB的地址空间。
  6. 大型紧凑型内存模型(.model large compact)

    • 段的大小

      • 代码段(CS): 1MB
      • 数据段(DS): 64KB
      • 堆栈段(SS): 64KB
      • 附加段(ES): 64KB
    • 特点

      • 代码段可以达到1MB。
      • 数据段、堆栈段和附加段每个都限制在64KB的范围内。
      • 使用远指针(far pointers)。
      • 适用于代码量较大但数据量较小的程序,数据段和堆栈段可以共享64KB的地址空间。
  7. 中型紧凑型内存模型(.model medium compact)

    • 段的大小

      • 代码段(CS): 64KB
      • 数据段(DS): 64KB
      • 堆栈段(SS): 64KB
      • 附加段(ES): 64KB
    • 特点

      • 代码段和数据段可以共享同一个64KB的地址空间。
      • 堆栈段和附加段可以共享同一个64KB的地址空间。
      • 使用近指针(near pointers)和远指针(far pointers)。
      • 适用于需要在数据段和堆栈段之间共享内存的程序。

基于内存模型定义的段

示例

.model small			;使用小内存模型.stack 256				;定义栈段的大小为256个字节,默认填充0.data 					;定义数据段d1 db 10 dup(0).code					;定义代码段start:;将ds段寄存器指向data段mov ax,@datamov ds,ax;退出程序mov ah,4chint 21hend start

等价于:

;关联段与段寄存器
assume cs:code, ds:data, ss:stack	stack segment				;定义栈段dw 256 dup(0)			;定义栈段的大小为256个字节,用0填充
stack endsdata segment				;定义数据段d1 db 10 dup(0)
data endscode segment				;定义代码段start:;将ds段寄存器指向data段mov ax,datamov ds,ax;将ss段寄存器指向stack段mov ax,stackmov ss,ax;退出程序mov ah,4chint 21h
data endsend start

使用.data和data segment的区别

当只有一个文件时没有区别,当多个文件定义数据段时,多个文件使用.data定义的数据段是连续的,而使用data segment则可能会在不同的内存中,每调用其他文件中的模块都需要重新设置ds段寄存器

使用外部代码块的伪指令

PROC和ENDP:定义一个过程

  • 作用:将重复使用的一段代码块拿出来作为一个子程序。 在汇编语言中,通常用术语过程(procedure)来指代子程序。在其他语言中,子程序也被称为方法或函数。

  • 过程的概念: 过程可以非正式地定义为:由返回语句结束的已命名的语句块。过程用 PROC 和 ENDP 伪指令来定义,并且必须为其分配一个名字(有效标识符) 。感觉也可以叫他函数

  • 语法:

    1. 近距离调用(不跨段)

      过程名 proc...		;具体的功能代码ret
      过程名 endp
      
    2. 远距离调用(跨段)

      过程名 proc far...		;具体的功能代码retf	;注意返回符的不同
      过程名 endp
      
  • 注意:

    1. 定义在过程中的标号只有过程内可见。

      标号类似于变量,过程类似函数,函数中定义的变量为局部变量,只有函数内可访问。标号之于过程与变量之于函数是一样的道理

    2. 如果想让过程中定义的标号全局可见,只需要多写一个冒号,如下

      过程名 proc标号1:		;该标号只有过程内部可见标号2::		;该标号全局可见ret
      过程名 endp
      

PUBLIC:声明一个标号可被其他模块调用

作用: 汇编伪指令,用于说明程序模块中的某个标号是可以被其他程序模块调用的。

格式:public 标号

EXTRN:声明一个标号是使用其他模块中的

作用: 汇编伪指令,用于说明程序模块中用到的标号是其他程序模块的

格式:

  1. extrn 标号:类型

    其中类型包括: near、far、byte、word、dword等。

    • near: 近调用,过程地址在同一个代码段内。

    • far: 远调用,过程地址可以位于不同的代码段内。

    • byte: 声明一个字节(8位)变量。

    • word: 声明一个字(16位)变量。

    • dword: 声明一个双字(32位)变量。

    • ...

  2. extrn 标号

    • smallcompact 内存模型中,默认类型为 near
    • largehuge 内存模型中,默认类型为 far
    • 对于数据变量(如 byte, word, dword),如果不指定类型,默认为 word

示例:将打印uin16_t范围的数字独立到另一个文件中

将上面打印uint16_t范围内的数字的代码放到单独的一个文件中,实现模块化编程

在同级目录中创建main.asmprint.asm两个文件,代码如下:

print.asm

; 声明该代码块可以被其他模块访问和调用
public Lib_print_uint16.model small                 ; 使用小内存模型,代码段和数据段各64KB
.stack 100h                  ; 定义堆栈段大小为256字节 (100h = 256).datastr    db 16 dup(0)      ; 存储待打印的字符,初始化为16个0字节的空间scale  dw 0              ; 进制,这里用于设置要转换的数字的进制,默认为0Number dw 0              ; 定义一个字(16位)变量Number,初始值为0,用于存放要打印的数字.code                        ; 代码段开始Lib_print_uint16 proc        ; 定义一个名为Lib_print_uint16的过程; 从栈中获取Number的地址pop    word ptr [str]    ; 将返回地址暂时存在str的前两个字中pop    [Number]          ; 将栈顶的值弹出并存储到Number变量中,这个值是要打印的16位无符号整数pop    [scale]           ; 将栈顶的值弹出并存储到scale变量中,这个值是要转换的进制push   word ptr [str]    ; 将之前弹出的返回地址重新压入栈中,以便正确返回调用点; 保存寄存器状态,防止过程执行期间修改寄存器值影响到调用方push   ax                ; 保存ax寄存器的值push   bx                ; 保存bx寄存器的值push   cx                ; 保存cx寄存器的值push   dx                ; 保存dx寄存器的值push   si                ; 保存si寄存器的值push   di                ; 保存di寄存器的值mov    ax, [Number]      ; 将Number的值加载到ax寄存器中,准备进行除法操作mov    bx, [scale]       ; 将scale的值加载到bx寄存器中,这里应该是要转换的进制数(如10进制)mov    si, 0             ; 初始化si为0,作为索引,用于向str中存储字符mov    dx, 0             ; 清空dx寄存器,用于除法操作前的准备。在div指令中,dx:ax会作为被除数
for1:mov    dx, 0             ; 在每次循环开始前,清空dx,确保除法操作的正确性div    bx                ; 用ax除以bx(scale),ax存放商,dx存放余数。这里将Number的值转换为字符表示add    dl, 30h           ; 将余数转换为ASCII码,30h是字符'0'的ASCII码,加30h后可以得到相应的数字字符cmp    dl, 58            ; 检查dl中的ASCII码是否大于'9'(58h是字符':'的ASCII码,但在这里应该是想检查是否大于'9')jb     No_16             ; 如果dl中的ASCII码不大于'9',跳转到No_16标签处add    dl, 7             ; 如果dl中的ASCII码大于'9',说明是10进制以上的转换,这里将dl加7h是想将字符转换为'A'-'F'(在16进制中)
No_16:mov    str[si], dl       ; 将转换后的字符存储到str数组中,si作为索引inc    si                ; si索引加1,指向数组的下一个位置cmp    ax, 0             ; 比较商ax是否为0jne    for1              ; 如果商ax不为0,继续循环for2:dec    si                ; 循环结束后,si指向的是数组的结束位置(多加了一次1),所以这里si减1指向数组的最后一个元素mov    dl, str[si]       ; 将str数组中si位置的字符加载到dl寄存器中,准备输出mov    ah, 2             ; 设置ah为2,这是DOS中断21h中用于输出字符的服务号int    21h               ; 调用DOS中断21h,输出dl中的字符cmp    si, 0             ; 比较si是否为0jne    for2              ; 如果si不为0,继续循环,输出str数组中的所有字符mov    dl, 0Ah           ; 将0Ah(换行符)加载到dl寄存器中,准备输出换行符mov    ah, 2             ; 设置ah为2,这是DOS中断21h中用于输出字符的服务号int    21h               ; 调用DOS中断21h,输出换行符mov    dl, 0Dh           ; 将0Dh(回车符)加载到dl寄存器中,准备输出回车符mov    ah, 2             ; 设置ah为2,这是DOS中断21h中用于输出字符的服务号int    21h               ; 调用DOS中断21h,输出回车符; 恢复寄存器的状态,确保调用方的寄存器未被修改pop    di                ; 恢复di寄存器的值pop    si                ; 恢复si寄存器的值pop    dx                ; 恢复dx寄存器的值pop    cx                ; 恢复cx寄存器的值pop    bx                ; 恢复bx寄存器的值pop    ax                ; 恢复ax寄存器的值ret                      ; 返回调用处Lib_print_uint16 endp        ; 结束过程定义end                          ; 标记程序结束

main.asm

.model small      ; 定义程序的模型为small模型,代码段和数据段各占64KB; 声明外部函数Lib_print_uint16,表示该函数在其他地方定义
extrn Lib_print_uint16:proc.stack 100h       ; 定义堆栈段的大小为256字节(100h = 256).data                            ; 数据段开始arr    dw 0, 1, 10 dup(0)Number dw 0                  ; 用于存储要打印的数字scale  dw 10                 ; 指定要打印的数的进制,默认10进制.code                                     ; 代码段开始print_uint16:                         ; 定义一个子程序print_uint16push [scale]push [Number]            ; 将Number的值压入堆栈,作为参数传递给Lib_print_uint16call Lib_print_uint16    ; 调用Lib_print_uint16函数,该函数负责打印指定长度的16位无符号整数ret                      ; 返回start:                                ; 定义程序的入口点mov  ax, @data           ; 将数据段的起始地址加载到AX寄存器中mov  ds, ax              ; 将AX寄存器中的地址复制到DS寄存器中,DS是数据段寄存器mov  ax, 43981           ; 将DI寄存器指向的数组元素的值加载到AX寄存器中mov  [Number], ax        ; 将AX寄存器中的值存储到变量Number中mov  [scale],16          ; 转成16进制后打印call print_uint16        ; 调用子程序print_uint16打印Numbermov  [scale],10          ; 转成10进制后打印call print_uint16        ; 调用子程序print_uint16打印Numbermov  [scale],8           ; 转成8进制后打印call print_uint16        ; 调用子程序print_uint16打印Numbermov  [scale],2           ; 转成2进制后打印call print_uint16        ; 调用子程序print_uint16打印Number; 程序结束mov  ah, 4Ch             ; 设置DOS程序结束功能号,4C00h表示程序正常结束int  21H                 ; 调用DOS中断21H,执行程序结束功能end start         ; 指定程序的结束点,并且程序从start标签开始执行

编译、链接、运行

  • 编译

    masm main.asm
    masm print.asm
    
  • 链接(注意,两个文件必须放一起进行链接)

    link main.obj print.obj
    
  • 运行

    main.exe
    
  • 运行结果如下:

    image

转换字母的大小写

通过观察ASLLC码可知,字母大写与小写只有第五位不一样,如下

image

当第五位为0时,该字母是大写,为1时为小写

所以在转换大小写时,只需要改变他们的第五位即可

示例:

data segmentstr  db 'Hello World', 0dh, 0ah, '$'    ;在data段定义要打印的字符串;其中0dh和0ah为回车和换行符;'$'为字符串结束符
data endscode segment;关联code段和data段的段地址寄存器assume cs:code, ds:data;定义print子程序,打印str字符串print:mov    dx,offset str       ;设置dx指向str字符串的起始位置mov    ah,09h              ;设置显示字符串的功能号int    21H                 ;调用BIOS中断retstart:;关联data段mov    ax,datamov    ds,ax;调用print子程序打印字符串,原始字符串call   print;将str字符串中的所有字母转换为大写字母mov    si,offset str       ;设置si指向str字符串的起始位置mov    cx,11               ;设置cx为字符串长度s1:   mov    al,[si]             ;将si指向的字符赋值给aland    al,0dfh             ;将al中的字符中的第5位(大写字母的第5位为0)置为0mov    [si],al             ;将al中的字符赋值给si指向的位置inc    si                  ;指向下一个字符的地址loop   s1                  ;循环执行s1,直到将替换完所有字符;调用print子程序打印字符串,转换为大写的字符串call   print;将str字符串中的所有字母转换为小写字母mov    si,offset str       ;设置si指向str字符串的起始位置mov    cx,11               ;设置cx为字符串长度s2:   mov    al,[si]             ;将si指向的字符赋值给alor     al,20h              ;将al中的字符中的第5位(小写字母的第5位为1)置为1mov    [si],al             ;将al中的字符赋值给si指向的位置inc    si                  ;指向下一个字符的地址loop   s2                  ;循环执行s2,直到将替换完所有字符;调用print子程序打印字符串,转换为小写的字符串call   print;退出程序mov    ah,4CHint    21H
code ends
end start

运行后结果如下

image

求数组的最大和最小值

这里的print.asm使用的是没有优化的版本,详细可见“分文件编写”

求最大值

data segment;定义数组,寻找该数组的最大值arr  db 2,1,9,4,6,7,8,5,3;定义字符串,显示数组的最大值,?代表占位符,等会替换为数组的最大值str  db 'sum of arr is: ', ?, 0dh, 0ah,'$'
data endscode segment;关联code段和data段的段地址寄存器assume cs:code, ds:data;定义print子程序,打印str字符串print:mov    dx,offset str         ;设置显示字符串的偏移地址mov    ah,09h                ;设置显示字符串的功能号int    21H                   ;调用BIOS中断retstart:;关联data段mov    ax,datamov    ds,axmov    si,offset arr         ;设置数组的偏移地址mov    ah,[si]               ;获取数组第一个元素,作为最大值mov    cx,9                  ;设置循环次数,也就是数组的长度sum:  mov    al,[si]               ;获取数组元素cmp    ah,al                 ;比较最大值和当前元素ja     big                   ;如果假设的最大值小于当前元素mov    ah,al                 ;则更新最大值,否则跳过big:  inc    si                    ;指向下一个元素,准备下一次比较loop   sumadd    ah,30h                ;将最大值转换为ASCII码mov    [str+15],ah           ;替换字符串的占位符为最大值call   print                 ;调用print子程序打印最大值;退出程序mov    ah,4CHint    21H
code ends
end start

运行结果如下:

image

求最小值同理,ja指令改为jb指令即可

数组求和

.model small      ; 定义程序的模型为small模型,代码段和数据段各占64KB; 声明外部函数Lib_print_uint16,表示该函数在其他地方定义
extrn Lib_print_uint16:proc.stack 100h       ; 定义堆栈段的大小为256字节(100h = 256).data                                         ; 数据段开始arr    db 1, 2, 3, 4, 5, 6, 7, 8, 9, 0Number dw 0                               ; 用于存储要打印的数字.code                                     ; 代码段开始print_uint16:                         ; 定义一个子程序print_uint16push [Number]            ; 将Number的值压入堆栈,作为参数传递给Lib_print_uint16call Lib_print_uint16    ; 调用Lib_print_uint16函数,该函数负责打印指定长度的16位无符号整数ret                      ; 返回start:                                ; 定义程序的入口点mov  ax, @data           ; 将数据段的起始地址加载到AX寄存器中mov  ds, ax              ; 将AX寄存器中的地址复制到DS寄存器中,DS是数据段寄存器mov  di, offset arr       ; 将数组arr的起始地址加载到DI寄存器中mov  ax, 0                ; 将AX寄存器清零,准备用于累加数组元素的值mov  cx, 10               ; 将CX寄存器设置为10,表示要累加的数组元素个数for:         add  al, [di]             ; 将DI寄存器指向的数组元素的值加到AL寄存器中adc  ah, 0                ; 将AL寄存器的进位加到AH寄存器中inc  di                   ; 将DI寄存器增加1,指向数组的下一个元素loop for                  ; 循环次数减1,如果不为零则跳转到for标签处继续执行mov  [Number],ax         ; 设置Numbercall print_uint16        ; 调用子程序打印; 程序结束mov  ah, 4Ch             ; 设置DOS程序结束功能号,4C00h表示程序正常结束int  21H                 ; 调用DOS中断21H,执行程序结束功能end start         ; 指定程序的结束点,并且程序从start标签开始执行

运行结果如下:

image

计算斐波那契数列

.model small      ; 定义程序的模型为small模型,代码段和数据段各占64KB; 声明外部函数Lib_print_uint16,表示该函数在其他地方定义
extrn Lib_print_uint16:proc.stack 100h       ; 定义堆栈段的大小为256字节(100h = 256).data                            ; 数据段开始arr    dw 0, 1, 10 dup(0)Number dw 0                  ; 用于存储要打印的数字.code                                     ; 代码段开始print_uint16:                         ; 定义一个子程序print_uint16push [Number]            ; 将Number的值压入堆栈,作为参数传递给Lib_print_uint16call Lib_print_uint16    ; 调用Lib_print_uint16函数,该函数负责打印指定长度的16位无符号整数ret                      ; 返回start:                                ; 定义程序的入口点mov  ax, @data           ; 将数据段的起始地址加载到AX寄存器中mov  ds, ax              ; 将AX寄存器中的地址复制到DS寄存器中,DS是数据段寄存器; 计算斐波那契数列mov  di, offset arr      ; 将数组arr的起始地址加载到DI寄存器中add  di, 4               ; 将DI寄存器增加4,指向数组的第三个元素(假设前两个元素已初始化)mov  cx, 10              ; 将CX寄存器设置为10,表示要计算的斐波那契数列的个数s:           mov  ax, 0               ; 将AX寄存器清零,准备用于计算斐波那契数列的当前值add  ax, [di-2]          ; 将DI寄存器指向的前一个元素的值加到AX寄存器中add  ax, [di-4]          ; 将DI寄存器指向的再前一个元素的值加到AX寄存器中mov  [di], ax            ; 将计算得到的斐波那契数列的当前值存储到数组arr的当前元素中add  di, 2               ; 将DI寄存器增加2,指向数组的下一个元素loop s                   ; 循环次数减1,如果不为零则跳转到s标签处继续执行mov  di, offset arr      ; 将数组arr的起始地址重新加载到DI寄存器中mov  cx, 12              ; 将CX寄存器设置为12,表示要打印的数组元素个数s1:          mov  ax, [di]            ; 将DI寄存器指向的数组元素的值加载到AX寄存器中add  di, 2               ; 将DI寄存器增加2,指向数组的下一个元素mov  [Number], ax        ; 将AX寄存器中的值存储到变量Number中call print_uint16        ; 调用子程序print_uint16打印Numberloop s1                  ; 循环次数减1,如果不为零则跳转到s1标签处继续执行; 程序结束mov  ah, 4Ch             ; 设置DOS程序结束功能号,4C00h表示程序正常结束int  21H                 ; 调用DOS中断21H,执行程序结束功能end start         ; 指定程序的结束点,并且程序从start标签开始执行

运行结果:

image

获取输入的字符串数量

获取键盘输入的字符串

  1. 在数据段定义输入缓冲区,用来存放输入的数据

    .data                               ; 数据段开始buffer db 255, 0, 255 dup(0)    ;定义输入缓冲区
    

    缓冲区第一个字节表示最大可接收的字符数量

    第二个字节表示实际接收到的字符数量

    第三个字节开始存储接收到的字符

  2. 将输入缓冲区的地址加载到dx中

    lea  dx,buffer
    ;等价于:mov dx,offset buffer
    
  3. 调用输入中断

    mov    ah,0ah                   ; 设置ah为0ah,这是DOS中断21h中用于输入字符串的服务号
    int    21h                      ; 调用DOS中断21h,执行字符串输入操作
    
  4. 在输入完成后记得打印回车换行,避免程序后面要打印字符时与输入的字符重叠

    mov  dl,0ah              ; 将0ah(换行符)加载到dl寄存器
    mov  ah,2                ; 设置ah为2,是DOS中断21h中用于输出字符的服务号
    int  21h                 ; 调用DOS中断21h,输出换行符
    mov  dl,0dh              ; 将0dh(回车符)加载到dl寄存器
    mov  ah,2                ; 设置ah为2,是DOS中断21h中用于输出字符的服务号
    int  21h                 ; 调用DOS中断21h,输出回车符
    

完整示例如下

.model small      ; 定义程序的模型为small模型,代码段和数据段各占64KB; 声明外部函数Lib_print_uint16,表示该函数在其他地方定义
extrn Lib_print_uint16:proc.stack 100h       ; 定义堆栈段的大小为256字节(100h = 256).data                               ; 数据段开始buffer db 255, 0, 255 dup(0)    ;定义输入缓冲区Number dw 0                     ; 用于存储要打印的数字.code                                     ; 代码段开始print_uint16:                         ; 定义一个子程序print_uint16push [Number]            ; 将Number的值压入堆栈,作为参数传递给Lib_print_uint16call Lib_print_uint16    ; 调用Lib_print_uint16函数,该函数负责打印指定长度的16位无符号整数ret                      ; 返回start:                                ; 定义程序的入口点mov  ax, @data           ; 将数据段的起始地址加载到AX寄存器中mov  ds, ax              ; 将AX寄存器中的地址复制到DS寄存器中,DS是数据段寄存器lea  dx,buffermov  ah,0ahint  21hmov  dl,0ah              ; 将0ah(换行符)加载到dl寄存器mov  ah,2                ; 设置ah为2,这是DOS中断21h中用于输出字符的服务号int  21h                 ; 调用DOS中断21h,输出换行符mov  dl,0dh              ; 将0dh(回车符)加载到dl寄存器mov  ah,2                ; 设置ah为2,这是DOS中断21h中用于输出字符的服务号int  21h                 ; 调用DOS中断21h,输出回车符mov  ah,0mov  al, [buffer+1]      ; 将DI寄存器指向的数组元素的值加载到AX寄存器中mov  [Number], ax        ; 将AX寄存器中的值存储到变量Number中call print_uint16        ; 调用子程序print_uint16打印Number; 程序结束mov  ah, 4Ch             ; 设置DOS程序结束功能号,4C00h表示程序正常结束int  21H                 ; 调用DOS中断21H,执行程序结束功能end start         ; 指定程序的结束点,并且程序从start标签开始执行

运行结果如下

image

冒泡排序

C语言版

升序排序,最小值左移

int arr[] = { 123, 45, 89, 192, 76, 158, 225, 34, 67, 189 };
int len = sizeof(arr) / sizeof(arr[0]);
int temp;
for (int i = 0; i < len; i++)
{// 每次循环比较出较小的值,然后左移,循环完成后该轮的最小值就放到i指向的位置了for (int j = len - 1; j > i; j--){// 想降序排序就改为大于号if (arr[j] < arr[j - 1]){temp = arr[j];arr[j] = arr[j - 1];arr[j - 1] = temp;}}
}for (int i = 0; i < len; i++)
{printf("%d, ", arr[i]);
}

汇编版

升序排序,最小值左移

.model small      ; 定义程序的模型为small模型,代码段和数据段各占64KB; 声明外部函数Lib_print_uint16,表示该函数在其他地方定义
extrn Lib_print_uint16:proc.stack 100h       ; 定义堆栈段的大小为256字节(100h = 256).data                                                        ; 数据段开始arr    db 123, 45, 89, 192, 76, 158, 225, 34, 67, 189    ; 定义一个包含10个无符号8位整数的数组Number dw 0                                              ; 定义一个无符号16位整数变量Number,用于存储要打印的数字.code                                     ; 代码段开始print_uint16:                         ; 定义一个子程序print_uint16push [Number]            ; 将Number的值压入堆栈,作为参数传递给Lib_print_uint16call Lib_print_uint16    ; 调用Lib_print_uint16函数,该函数负责打印指定长度的16位无符号整数ret                      ; 返回start:                                ; 定义程序的入口点mov  ax, @data           ; 将数据段的起始地址加载到AX寄存器中mov  ds, ax              ; 将AX寄存器中的地址复制到DS寄存器中,DS是数据段寄存器; 冒泡排序算法的实现mov  cx,10               ; 将CX寄存器设置为10,表示数组的长度mov  di,0                ; 将DI寄存器设置为0,作为外层循环的计数器for1:        push cx                  ; 保存CX寄存器的值,用于外层循环的计数mov  cx,9                ; 将CX寄存器设置为9,作为内层循环的计数,减少比较次数for2:        cmp  cx,di               ; 比较CX和DI的值,判断是否需要继续内层循环jna  for2_end            ; 如果CX小于或等于DI,跳转到for2_end结束内层循环mov  si,cx               ; 将CX的值复制到SI,作为数组索引mov  al,arr[si]          ; 将数组arr中索引为SI的元素加载到AL寄存器cmp  al,arr[si - 1]      ; 比较AL和arr[SI-1]的值,判断是否需要交换ja   No_Swap             ; 如果arr[si] 大于arr[si-1] ,跳转到No_Swap,不进行交换;交换arr[si]和arr[si-1]xchg arr[si - 1],al      ; 将AL中的值与arr[si-1]交换,因为此时al中存放的是arr[si]的值mov  arr[si],al          ; 此时al中的值是arr[si-1],放到arr[si]中,至此完成交换No_Swap:     loop for2                ; 内层循环计数器CX减1,如果CX不为0,继续for2循环for2_end:    pop  cx                  ; 恢复CX寄存器的值,用于外层循环的计数inc  di                  ; 外层循环计数器DI加1loop for1                ; 外层循环计数器CX减1,如果CX不为0,继续for1循环; 打印排序后的数组mov  cx,10               ; 将CX寄存器设置为10,表示数组的长度mov  di,0                ; 将DI寄存器设置为0,作为数组索引mov  ax,0                ; 清空AX寄存器s:           mov  al,arr[di]          ; 将数组arr中索引为DI的元素加载到AL寄存器mov  ah,0                ; 清空AH寄存器,确保AX是一个完整的16位无符号整数mov  [Number], ax        ; 将AX寄存器中的值存储到变量Number中call print_uint16        ; 调用子程序print_uint16打印Number的值inc  di                  ; 数组索引DI加1loop s                   ; 内层循环计数器CX减1,如果CX不为0,继续s循环; 程序结束mov  ah, 4Ch             ; 设置DOS程序结束功能号,4C00h表示程序正常结束int  21H                 ; 调用DOS中断21H,执行程序结束功能end start         ; 指定程序的结束点,并且程序从start标签开始执行

运行效果如下:

image

降序排序,最大值左移

将如下位置的代码由ja改为jb即可

image

运行效果如下:

image

C反汇编:函数调用

STOS:串行存储指令

作用:将ax中的数据,放到es:[di]指向的地址中,并且di自增(16位系统默认自增2,32位默认自增4)

格式:sots 数据类型 ptr es:[di]

其中数据类型就表示了di自增的数量,buty类型自增1,word类型自增2,... ...

示例

  • ;将al中的值放到es:[di]指向的地址处,并且di自增1
    stos byte ptr es:[di]
    stosb
    

    上面两条指令等价。

    下面的指令是上面指令的缩写

  • ;将ax中的值放到es:[di]指向的地址处,并且di自增2
    stos word ptr es:[di]
    stosw
    
  • ;将eax中的值放到es:[di]指向的地址处,并且di自增4
    stos dword ptr es:[di]
    stosd
    

    这是在32位系统中的指令

REP:重复执行指令

作用: 会根据计数寄存器CX中的值来指定次数,重复执行指令

示例:将es:[0000h]到es:[0014h]之间的内存初始化为0xcccc

mov    cx,10		;指定rep要重复的次数
mov    ax,0cccch	;指定要初始化的值
lea    di,[0]		;指定要初始化的地址
rep    stosw

运行后结果如下:

image

使用vs查看反汇编、内存、寄存器等

选择要查看的地方打上断点后运行,系统最好选择:x86,因为x86是32位,x64是64位,反汇编的代码会有所出入

image

然后选择:调试--->窗口,就可以选择打开反汇编、内存、寄存器等等界面了

image

最后效果如下

image

反汇编之函数调用

注意:之前在8086学的寄存器都是16位的,用ax、bx、cx这些表示。而在32位系统中这些寄存器都是32位的,用eax、ebx、ecx等等表示,寄存器前面加上e就说明该寄存器是32位的

以如下函数为例

// 定义
int My_Sum(int x, int y)
{int z = 0;z = x + y;return z;
}// 调用
My_Sum(1, 2);
  1. 参数入栈,顺序是从左往右,然后跳转到函数所在的位置

    push 2
    push 1
    call My_Sum
    

    如下:

    image

  2. 创建栈帧

    push ebp					;bp寄存器的值入栈
    mov  ebp,esp				;sp赋值bp,让bp作为该栈帧的栈底
    sub  esp,0cch				;sp减去0xcc,表示该栈帧的大小为:0xcc;地址寄存器入栈
    push ebx						
    push esi
    push edilea  edi,[ebp - 0ch]		;di指向距离栈底0x0c的地址,也就是说该函数可用的空间为12个字节(这里根据函数所需用到的空间来定)
    mov  ecx,3					;指定rep重复的次数
    mov  eax,0cccccccch			;指定初始化的值
    rep stos dwrod ptr es:[edi]	;eax占4个字节,重复三次,刚好初始化从栈底开始的12个字节的空间为0xcc
    

    如下:

    image

  3. 创建局部变量

    mov dword ptr [ebp - 8],0	;从距离栈底8个字节位置开始创建四个字节的局部变量0x00000000
    

    如下:

    image

    image

  4. 调用参数

    mov eax,dword ptr [ebp + 8]		;取出参数:1([epb+4]是返回地址),并放到eax中
    add eax,dword ptr [ebp + 12]	;取出参数:2,与eax相加后结果放在eax中
    mov dword ptr [ebp - 8],eax		;将计算的结果放到刚刚创建的变量中
    

    如下:

    image

    image

  5. 获取返回值

    mov eax,dword ptr [ebp - 8]		;将返回结果放到eax中
    

    image

  6. 函数返回

    ;恢复地址寄存器
    pop edi
    pop esi
    pop edxadd esp,0cch	;销毁栈帧(这也是为什么局部变量在函数运行完毕后会被销毁)
    mov esp,ebp
    pop ebp			;恢复ebp
    ret				;返回函数调用的地方
    add esp,8		;销毁传的参数,让esp和ebp回到调用函数前的状态
    

    如下:

    image

    image

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/859974.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

可信执行环境

一、隐私计算与可信执行环境 1. 背景:随着云计算和大数据的普及,用户之间需要进行隐私数据的共享与协作,这些数据被上传到云端进行计算和处理。 但是,由于隐私数据交由不可信的第三方存储和管理,用户隐私数据面临被泄露的风险,公民的生命和财产安全乃至国家的安全都受到不…

Foldermove 轻松地把电脑里的软件搬到另一个硬盘,甚至是U盘里

Foldermove 大家好,今天我要介绍一个超实用的小工具,它能让你轻松地把电脑里的软件搬到另一个硬盘,甚至是U盘里,听起来是不是很酷? ●软件简介 软件名:FolderMove 体积:只有201kb,轻得像羽毛。 版本:v3.0 适用系统:Windows ●使用体验 这个小工具,体积小到几乎可以…

ping 工具的使用

一、ping基本使用详解在网络中ping是一个十分强大的TCP/IP工具。它的作用主要为:1、用来检测网络的连通情况和分析网络速度2、根据域名得到服务器IP3、根据ping返回的TTL值来判断对方所使用的操作系统及数据包经过路由器数量。 bytes值:数据包大小,也就是字节。 time值:响…

二维、三维组件融合 720三维全景沉浸式实景体验

本系统通过数字孪生技术,实现小区楼盘系统的可视化展示,整合楼盘内各个系统的数据源,将楼盘模型与房间模型、720三维全景图相结合,实现了从楼盘周边到室内布局的全方位展示,为购房者提供全方位的可视化信息。整个项目分为四个功能模块,分别为楼盘概览、楼盘规划、归家动线…

2024-2025-1 20241318 《计算机基础与程序设计》第十四周学习总结

这个作业属于哪个课程 https://edu.cnblogs.com/campus/besti/2024-2025-1-CFAP(这个作业要求在哪里 https://www.cnblogs.com/rocedu/p/9577842.html#WEEK14这个作业的目标 <学习《C语言程序设计》第13-14章并完成云班课测试>作业正文 https://i.cnblogs.com/posts/edi…

智谱开源 CogAgent-9B,让 AI「看懂」屏幕;Anthropic:大多数任务无需复杂 AI 智能体

开发者朋友们大家好:这里是 「RTE 开发者日报」 ,每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE(Real-Time Engagement) 领域内「有话题的 新闻 」、「有态度的 观点 」、「有意思的 数据 」、「有思考的 文章 」、「有看点的 会议 」,但内容仅代表编辑…

GitLab社区版CI/CD实现

准备工作: 系统环境:CentOS Linux release 7.9.2009 (Core) 安装包:gitlab-ce-11.1.1-ce.0.el7.x86_64.rpm 和 gitlab-runner-11.1.1-1.x86_64.rpmCI/CD流程:代码推送: 开发者将代码推送到GitLab的远程仓库。触发CI/CD Pipeline: 当代码被推送到GitLab仓库时,GitLab会检…

宝塔面板解压文件无响应或需要重启才能解压

您好,关于您提到的宝塔面板在解压文件时出现无响应或需要重启才能继续解压的问题,这可能是由以下几个原因引起的。首先,我们需要了解一些背景信息来更好地解决这个问题。压缩包文件异常: 压缩包文件本身可能存在损坏或不完整的情况,导致宝塔面板在解压过程中遇到错误而无法…

1.什么是CSS

1.Cascading Style Sheet 层叠级联样式表 CSS:表现(美化网页) 字体,颜色,边距,高度,宽度,背景图片,网页定位,网页浮动....2.发展史 CSS1.0 只能美化字体 CSS2.0 DIV(块)+CSS,html与css结构分离的思想,网页变得简单,利于SEO CSS2.1 浮动,定位 CSS3.0 圆角,阴影,动画…

【Spring】三级缓存解决循环依赖问题

参考地址: Spring循环依赖:https://zhuanlan.zhihu.com/p/700890658 Spring三级缓存解决循环依赖的问题:https://blog.csdn.net/Trong_/article/details/134063622================================================================== 1.什么是循环依赖?1>说白是一个或…

uni-app 设置多语言切换uni-i18n插件

安装uni-i18n插件npm install uni-i18nmain.js文件中引入并初始化VueI18n///main.js import messages from ./language/index let i18nConfig = {locale: uni.getLocale(),messages }import Vue from vue import VueI18n from vue-i18n import App from ./App Vue.use(VueI18n)…

查询数据库开始时间和结束时间字段中包括了给定时间区间的数据

表数据示例: 查询区间:2024-12-03 10:00:00 - 2024-12-06 18:00:00 mysql示例:SELECT * FROM time_test WHERE ((start_time > 2024-12-03 10:00:00 AND (2024-12-06 18:00:00 > end_time OR ( 2024-12-06 18:00:00 > start_time AND 2024-12-06 18:00:00 < e…