Assembly_Practical1

// CPT101 Practical Exercise 1.cpp : This file contains the 'main' function. Program execution begins and ends there.
//

#include <iostream>

int main()
{
	char message1[] = "Give me a first number: ";
	char message2[] = "\bGive me a second number: ";
	char message3[] = "\nThe numbers are equal!\n";
	char message4[] = "\nThe numbers are not equal!\n";
	char message5[] = "Type in any integer and press ENTER key to finish: ";
	char format[] = "%d";	// format string for the scanf function

	int first;
	int second;
	int end;

	_asm {
		lea		eax, message1
		push	eax
		call	printf; printing the first message
		add		esp, 4

		lea		eax, first
		push	eax
		lea		eax, format
		push	eax
		call	scanf_s; reading the first number
		add		esp, 8

		lea		eax, message2
		push	eax
		call	printf; printing the second message
		add		esp, 4

		lea		eax, second
		push	eax
		lea		eax, format
		push	eax
		call	scanf_s; reading the second number
		add		esp, 8

		mov		eax, first
		sub		eax, second; compare two numbers, jump to nequal if flag is not zero
		jnz		nequal

		equal:
			lea		eax, message3
			push	eax
			call	printf; printing the third message
			add		esp, 4
			jmp		finish

		nequal:
			lea		eax, message4
			push	eax
			call	printf; printing the fourth message
			add		esp, 4

		finish:
			lea		eax, message5
			push	eax
			call	printf; printing the fifth message
			add		esp, 4

			lea		eax, end
			push	eax
			lea		eax, format
			push	eax
			call	scanf_s; reading any integer to end program
			add		esp, 8
	}
	return 0;
}

// Run program: Ctrl + F5 or Debug > Start Without Debugging menu
// Debug program: F5 or Debug > Start Debugging menu

// Tips for Getting Started: 
//   1. Use the Solution Explorer window to add/manage files
//   2. Use the Team Explorer window to connect to source control
//   3. Use the Output window to see build output and other messages
//   4. Use the Error List window to view errors
//   5. Go to Project > Add New Item to create new code files, or Project > Add Existing Item to add existing code files to the project
//   6. In the future, to open this project again, go to File > Open > Project and select the .sln file




你这段混合 C++ + 内联汇编的程序从头到尾分解清楚。目标:你看完能知道每条指令在干嘛、栈上参数长啥样、怎样改成“三路比较(=,>,<)


这段程序做什么(一句话)

在控制台上提示并读入两个整数,比较它们是否相等,打印相等或不相等的消息,然后再读一个整数以结束程序。核心比较逻辑由内联汇编 _asm { ... } 完成。

先提醒:这段代码使用 MSVC 的 x86 内联汇编 (_asm) —— 只能在 32-bit (Win32) 编译目标下工作。若你编译为 x64,VS 会报错(x64 不支持内联 asm)。


代码总体结构速览

  • 前半部分(C++):声明若干 char[] 消息字符串、format 字符串、以及 int first, second, end 三个整型局部变量。
  • _asm { ... } 块:一系列汇编指令调用 printf/scanf_s,完成显示提示、读数、比较、分支、再读数以结束程序。
  • 程序返回 0 结束。

逐段解释(带注释与推理)

先贴出关键片段并逐句解释(汇编行按出现顺序讲):

_asm {
    lea     eax, message1
    push    eax
    call    printf
    add     esp, 4
  • lea eax, message1lea(load effective address)把 message1 的地址装入 EAX。注意不是把字符串内容装入,是地址。

  • push eax:把 message1 的地址压入栈(参数传递)。

  • call printf:调用 C 运行时函数 printf。关于调用约定(32-bit 下常见的是 cdecl):

    • 参数按 右到左(right-to-left)顺序压栈;
    • call 指令会把返回地址压入栈并跳到函数;
    • caller(调用者)负责清理栈,所以紧接着有 add esp, 4 来平衡压入的参数(一个参数 => 4 字节)。
  • add esp, 4:把栈指针上调 4 字节,清掉刚刚传入的 printf 参数。


接下来读第一个整数:

    lea     eax, first
    push    eax
    lea     eax, format
    push    eax
    call    scanf_s
    add     esp, 8
  • 这是典型的 右到左压栈scanf_s(format, &first) 的源码参数顺序是 format &first ,因此汇编需要先 push &first,再 push format。这样在函数里 [ESP+4] 指向 format[ESP+8] 指向 &first(call 后栈上多了 return address)。
  • scanf_s:MSVC 提供的更“安全”的输入函数。对于 %d 不需要额外传入缓冲区大小(与 %s 不同,%sscanf_s 中通常需要多传一个尺寸参数)。
  • add esp, 8:清理两个参数(2 × 4 bytes)。

(然后打印第二个提示、读第二个数,方式与上述对称 —— 不再重复说明。)


比较逻辑这一段:

    mov     eax, first
    sub     eax, second
    jnz     nequal

equal:
    lea     eax, message3
    push    eax
    call    printf
    add     esp, 4
    jmp     finish

nequal:
    lea     eax, message4
    push    eax
    call    printf
    add     esp, 4

finish:
    ... (等待结束输入)
  • mov eax, first:把变量 first加载到 EAX。注意:这里不是 lea,而是把数值取出。

  • sub eax, second:用 eax = eax - second。这条指令会影响 CPU 的状态标志(Zero Flag、Sign Flag、Overflow Flag 等)。

  • jnz nequaljnz = jump if not zero(当 ZF=0 时跳转)。如果 eax - second != 0(也就是两个数不等),跳到 nequal

    • 如果 eax - second == 0,则不跳,继续执行 equal: 分支,打印 “The numbers are equal!”。
  • jmp finish:打印相等消息后直接跳到 finish,避免继续执行下面的不等分支。

  • nequal 标签下打印不相等信息。

关键点:用 mov + sub 然后 jnz 判断等不等是可行的,但 cmp 指令更常见(cmp a,b 只是做减法更新标志而不改变寄存器值)。mov+sub 会把结果留在 EAX(如果你之后还需要 first 的原值,必须注意)。


一些重要的汇编/调用细节(新手易犯混淆)

  1. 为什么参数要右到左压栈?
    对于像 printf(format, a, b) 这样多个参数,C 的调用约定(cdecl)规定先把最右边的参数压栈,这样函数内 [ESP+4] 是 format,第一个实际参数在后面依次排列。汇编写调用时必须遵守这个顺序。

  2. add esp, N 是谁来做的?
    在 cdecl 下 调用者 清理栈(所以你看到 add esp, 4/8)。在 stdcall 下通常由被调用者清理(但 MSVC 的 stdcall 在 C++ 函数里常见于 Windows API)。

  3. lea vs mov

    • lea eax, var → 把 地址放进 eax。用于传地址作为参数(push eax 之后就是 printf/scanf 的地址参数)。
    • mov eax, var → 把 变量的值放进 eax。用于需要读取数值进行运算或比较的场合。
  4. 寄存器作用:

    • EAX 常用作临时寄存器,也用于函数返回值(return in EAX)。
    • EBX/ECX/EDX 等寄存器在调用约定中有“易失/非易失”区分——简单起见,记住 EAX/ECX/EDX 是被认为“caller-save”(调用者需要在调用外存回值),不必太纠结现在。
  5. 注意字符串声明的存放位置
    这些 char message1[] = "..." 放在函数里,编译器可能把它们安置在可写的数据区或在栈上初始化,具体视编译器实现。关键:它们有地址,lea 可以取到。

  6. scanf_s 的特殊性

    • 对于 %d 没有额外要求;对 %s%c 等需要传入缓冲大小作为额外参数(MSVC 的安全版本规范)。
  7. message2 里开头是 \b(退格)
    这很可疑 —— \b 会把光标左移一格,但通常不会清除字符。很可能作者想写 \n(换行)或根本不需要 \b。这看起来像个小错误,可根据显示效果决定是否改为 "\nGive me a second number: " 或直接 "Give me a second number: "


栈上的参数示意(举例帮助理解 push 顺序)

假设调用 scanf_s(format, &first) 时执行:

push addr_of_first   ; esp -> addr_of_first
push addr_of_format  ; esp -> addr_of_format
call scanf_s         ; call pushes return address; 在函数里 [ESP+4] = addr_of_format

函数内部能通过 [ESP+4] 访问 format,通过 [ESP+8] 访问 &first。这就是右到左压栈的效果。


如何把它改成“三路比较”(equal / first greater / second greater)

你之前任务里要求区分三种情况,这里给出推荐的做法(更规范,用 cmp + 有符号条件跳转):

示例汇编片段(替换原来的 mov/sub/jnz 部分):

    mov     eax, first
    cmp     eax, second    ; 等价于 compare first and second
    je      equal_case
    jg      first_greater  ; jg = jump if greater (signed)
    jl      second_greater ; jl = jump if less (signed)

equal_case:
    lea     eax, message3
    push    eax
    call    printf
    add     esp, 4
    jmp     finish

first_greater:
    ; push message "First one greater"
    lea     eax, msg_first
    push    eax
    call    printf
    add     esp, 4
    jmp     finish

second_greater:
    lea     eax, msg_second
    push    eax
    call    printf
    add     esp, 4

注意:

  • 使用 cmp 后再用 je/jg/jl 是常见且语义清晰的方法(jg 是有符号比较,若你的数据是无符号,用 ja / jb)。
  • cmp a,b 相当于做 a-b 并更新标志位,但不改变寄存器值(比 mov/sub 更常见且安全)。

小优化 / 常见错误提示(直说)

  1. 如果你在 x64 下编译,_asm 会失败 —— 改用 Win32 或写单独的 .asm 文件并用 MASM(或用 intrinsics / 编译器生成的汇编)。
  2. message2\b 很可能是笔误;如果你看到终端有怪异光标或输出叠在一起,改成 \n 或删掉它。
  3. 在 C++ 里习惯性 include <cstdio>(或 <stdio.h>)来声明 printf/scanf_s<iostream> 并不会声明这两个 C 函数。建议把 #include <cstdio> 加上,避免编译器警告。
  4. 比较用 cmp 更清晰,且可以直接使用 jg/jl/je 这类条件跳转判断大小与等于(注意有符号 vs 无符号)。

调试小贴士(在 Visual Studio)

  • 编译为 Win32(Project → Properties → Platform = Win32),否则内联 asm 会出问题。

  • _asm 块中设置断点,使用 单步(F10 / F11)

    • F11 进入(Step Into)会进入 scanf_s/printf(你一般不想进 C 库),用 F10(Step Over)更合适。
  • 打开 Registers 窗口(Debug → Windows → Registers)观察 EAX/EBX/... 的变化。

  • 打开 Autos/Locals/Watch 观察变量 firstsecond 在内存位置的值。

  • 当你 push 参数后、call 前,可以在内存窗口查看栈里保存的参数地址与内容,确认顺序没问题。


总结(方便记忆的要点)

  • 这是 32-bit inline asm;参数用栈传,调用者清栈。
  • lea 取得地址, mov 取得值;cmp/sub 更新标志用于条件跳转。
  • jnz/je/jg/jl 是基于 CPU 标志做跳转;选 jg/jl 做大小比较更语义化。
  • 小错误:message2\b 很可能是笔误;改成 \n 或去掉。
  • 若要“三路比较”,请用 cmp + je/jg/jl

// Practical1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>

int main()
{
    char message1[] = "Give me a first number:";
    char message2[] = "\nGive me a second number:";
   /* char message3[] = "\nThe numbers are equal!\n";
    char message4[] = "\nThe numbers are not equal!\n";
    char message5[] = "Type in any integer and press RETURN key to finish:";*/

    char message3[] = "\nThe numbers are equal!\n";
    char message4[] = "\nFirst number is greater!\n";
    char message5[] = "\nSecond number is greater!\n";
    char message6[] = "Type in any integer and press RETURN key to finish:";

    char format[] = "%d"; // scanf 函数的格式字符串

    int first;
    int second;
    int end;

    _asm {
        lea eax, message1
        push eax; 打印第一条消息
        call printf
        add esp, 4

        lea eax, first
        push eax
        lea eax, format; 读取第一个数字
        push eax
        call scanf_s
        add esp, 8

        lea eax, message2
        push eax; 打印第二条消息
        call printf
        add esp, 4

        lea eax, second
        push eax
        lea eax, format; 读取第二个数字
        push eax
        call scanf_s
        add esp, 8

        /*mov eax, first
        sub eax, second
        jnz nequal; 比较两个数字,跳转到相应部分

        equal :
        lea eax, message3
            push eax; 打印“数字相等”
            call printf
            add esp, 4
            jmp finish; 跳转到结束

        nequal :
            lea eax, message4
            push eax; 打印“数字不相等”
            call printf
            add esp, 4*/
        ; == 三路比较逻辑 ==
        mov  eax, first
        cmp  eax, second
        je   equal_case 
        jg   first_greater 
        jl   second_greater 

        equal_case :
            lea   eax, message3
            push  eax
            call  printf
            add   esp, 4
            jmp   finish

        first_greater :
            lea   eax, message4
            push  eax
            call  printf
            add   esp, 4
            jmp   finish

        second_greater :
            lea   eax, message5
                push  eax
                call  printf
                add   esp, 4
                jmp   finish
        ;================
        finish :
        ; 打印结束提示
         //lea eax, message5
        lea eax, message6
        push eax; 打印第五条消息
        call printf
        add esp, 4

        ; 读一个整数以结束程序
        lea eax, end
        push eax
        lea eax, format
        push eax
        call scanf_s
        add esp, 8
    }

    return 0;
}

// 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单
// 调试程序: F5 或调试 >“开始调试”菜单

// 入门使用技巧: 
//   1. 使用解决方案资源管理器窗口添加/管理文件
//   2. 使用团队资源管理器窗口连接到源代码管理
//   3. 使用输出窗口查看生成输出和其他消息
//   4. 使用错误列表窗口查看错误
//   5. 转到“项目”>“添加新项”以创建新的代码文件,或转到“项目”>“添加现有项”以将现有代码文件添加到项目
//   6. 将来,若要再次打开此项目,请转到“文件”>“打开”>“项目”并选择 .sln 文件

posted @ 2025-10-06 21:15  ji415  阅读(15)  评论(0)    收藏  举报