案例:计算两个变量差的绝对值
一、简化代码的过程
16位汇编
1 | ;程序名:test.asm |
32位汇编
1 | ;--------------------------- |
编译器新增功能
- 段由完整定义(
data segment...data ends
)改为简化定义(.data) - 32位汇编开始习惯使用
main PROC...main ENDP
来区别程序执行的主函数和其它函数。 - 变量定义出现了 “有符号变量” 和 “无符号变量”
- 将 cmp 指令简化为宏指令
.if
….else
….endif
- 根据定义的变量是否为有符号变量自动选择相应的 “转移指令”
- 为符合人类的常规逻辑来执行相应的语句块,使用
<
、>
….等符号来表示大小等,实际汇编未达到这种效果需要使用的是相反的跳转指令。 - call 指令用 INVOKE 伪指令代替。
1
2
3
4
5
6
7;INVOKE ExitProcess,0
push 0
call ExitProcess
相当于
mov ax,4c00h
int 21h
C语言
1 | /*程序名:test.c*/ |
编译器优化后的功能
- 在C语言中隐藏了程序的执行细节(消除了内存地址、寄存器和汇编指令),语法简化且详细,学习的内容。
- 有无符号的变量定义从 “SDOWRD” 变成 “int”,”DWORD” 变成 “unsigned int”。
main PROC...main ENDP...END main
函数结构被main(){...}
代替。.if...else...endif
语句,简化为if...else
语句。
总结
- C语言是建立在汇编语言基础上,翻译成汇编语言,然后在机器上执行高级语言
- C语言比汇编语言隐藏了程序执行的细节,再就是语法规则比汇编语言规定的更详细。
- C语言保留了汇编语言操作内存的特性
- C语言更接近于人类语言,代码编写的效率大大提高,更有利于进行更大规模的软件开发。
这一切都是建立在更为强大的编译器的基础之上
二、指针和变量
32位汇编
1 | ;--------------------------- |
C语言
1 | /*程序名:test.c*/ |
总结
汇编语言中变量a既表示地址,也表示地址存放的内容。
C语言中:变量名a表示内存地址,&a表示地址汇编语言中的数组名b既表示数组的起始地址,也表示该数组第一个元素的值。
C语言中:数组名b表示地址,&b表示地址,*b表示数组的第一个元素值。汇编语言中字符串变量名message表示该字符串起始地址,也表示字符串首字符。
C语言中:字符串变量名c和&c都表示字符串数组的起始地址,*c表示字符串数组的第一个元素的值。
总结:C语言规定更为详细,将汇编语言中的地址和地址对应的内容做了区分;C语言中的指针就是内存地址,对指针的操作,就是直接对内存地址的操作。
三、函数与过程
汇编语言
寄存器传参
1 | ;--------------------------- |
堆栈传参
1 | ;--------------------------- |
这里我尽量用伪代码,这样和C语言对比看起来更清晰。需要注意的是这里的伪代码对应的汇编是什么需要清楚。
C语言
有返回值函数
1 | /*程序名:test2.c*/ |
无返回值函数
1 | /*程序名:test3.c*/ |
总结
在汇编中函数的参数入栈顺序可由程序编写者定义,后续为了规范操作定义了函数的调用约定,以此来规范函数写法。
函数名即函数的地址,在call调用函数时会push压入call的下一条指令的地址。而函数中的ret指令,返回到之前压入的地址处,从而达到函数调用的效果。在后面编译器的优化中,call指令可使用伪指令INVOKE代替,但使用 INVOKE 伪指令时就需要使用伪指令 PROTO 声明函数原型。
利用 mov 指令将参数传到寄存器中,表示寄存器传参;利用push指令将参数传到堆栈中,表示堆栈传参。入栈n个参数,函数就需要
ret n*4
或者在call函数后添加add esp,n*4
,这就是堆栈平衡(保证堆栈执行后续代码时还原成当前函数执行之前的状态)。
四、创建多模块程序-外部函数与过程的调用
外部函数与过程的调用(传统汇编)
1 | ;--------------------------------------------- |
1 | ;--------------------------------------------- |
知识点回顾:
PUBLIC 可将变量和过程模块(函数)设为公共的,方便外部调用。其次,默认情况下masm中所有的过程名(函数)都是公共的,这使得同一程序的其他任意模块都可以调用这些过程。
EXTERN 问指令用于在调用当前模块之外的过程时使用。它可以指定外部过程的名字和外部过程堆栈的大小。
过程名的后缀 @n 确定了已声明参数占用的堆栈空间总量。如果使用的是基本 PROC 伪指令,没有声明参数,那么 EXTERN 中的每个过程名后缀都为 @0。如果声明过程中使用的是带参数的扩展的 PROC 伪指令,那么每个参数占用 4 字节。
EXTERN 访问外部变量:EXTERN [变量名]:[变量类型]
外部函数与过程的调用(高级汇编)
1 | ;--------------------------------------------- |
1 | ;--------------------------------------------- |
知识点回顾:
- 多模模块程序可以使用 MASM 的高级伪指令 INVOKE、PROTO 和 扩展PROC伪指令创建。与使用call和EXTERN的传统方式相比,其主要优点在于能够匹配INVOKE伪指令中传递的参数列表和PROC伪指令声明的参数列表检查是否一致。
总结
C语言函数的外部调用和32位汇编使用高级伪代码进行外部调用是一样,只是C中更加人性化了,看起来更加简洁。
五、段的定义
汇编和C的段的定义
16位汇编:段的完整定义
32位汇编:段的简化定义
C语言:段的简化定义
汇编和C的段的划分
- C语言中分为5个区域:堆区、栈区、代码区、静态区(全局变量),常量区(常量值,包括字符串)
- 对应汇编中的5个区域:堆区、栈区、代码区、初始化数据与未初始化数据区
汇编和C的各个段的区别
汇编语言:
- 堆区:程序员自己申请的内存分配,完成特定的任务,或者存放数据
- 栈区:系统分配的内存
- 代码区:代码
- 数据段(已分配)
- 未初始化的数据段BSS:未初始化全局变量,未初始化全局静态变量
C语言
- 堆区:一样
- 栈区:一样,局部变量,函数参数
- 代码区:代码
- 静态区:已初始化的全局变量、已初始化的全局静态变量、局部静态变量、常量数据、全局未初始化的变量、静态未初始化变量
- 常量区:字符串常量
堆区和栈区在汇编中统称为堆栈,这两个区没有分别就是一块内存。后面在 C 中将它们区分开来,在C中栈区主要用来存放局部变量(存放小的数据块),当我们个人需要一块大的内存时,我们就需要自己申请,这就是堆区。栈区是由系统来维护,而堆区是由程序员来维护。
- 本文标题:C语言进阶-汇编和C
- 本文作者:9unk
- 创建时间:2023-08-28 16:21:00
- 本文链接:https://9unkk.github.io/2023/08/28/c-yu-yan-jin-jie-hui-bian-he-c/
- 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!