如何培养对 LC-3 汇编代码的直觉?
How to develop an intuition to code in LC-3 Assembly?
我了解了 LC-3 的工作原理,但我终生无法弄清楚如何在 LC-3 汇编中编码。我的目标是能够编写简单的程序,例如生成斐波那契数或对数组进行排序。
有人可以给我指点资源来学习这个吗?我精通 Python 和 Java,所以我很清楚这些问题背后的基本逻辑。
学习汇编语言有几个方面,汇编语言是处理器机器代码的人类可读版本。
其他语言基本上都是逻辑层面的,而机器码很大程度上是物理层面的
一方面,这尤其体现在存储概念上的差异:
- 逻辑变量与 CPU 物理寄存器和内存
- 逻辑变量是动态的,CPU寄存器和内存是固定的,永久的
- 具有类型的变量与具有位的物理存储
因此,当我们编写汇编语言时,我们翻译我们的伪代码:具有大量有限生命周期类型变量的逻辑代码,部分是通过将逻辑变量映射到固定的物理资源。通常有比 CPU 寄存器更多的变量,尤其是当某些寄存器有专用用途时,如堆栈或 return 地址。
另一方面,今天的其他语言通常采用结构化编程,而在汇编 language/machine 代码中我们有 if-goto-label。
所有结构化语句在 if-goto-label 中都有翻译。每个翻译都是将结构化形式的模式转换为 if-goto-label 形式的模式。正确地遵循这些模式,您将重现伪代码的控制流 — 在这里很容易走捷径并犯下令人困惑的错误,因此我鼓励在这里采用有条不紊的方法。
其他语言有丰富的表达式:有许多优先级的运算符,并且可以使用 ()
的复杂程度。机器代码的指令(通常)最多需要 3 个操作数。
函数调用、堆栈框架、参数传递、return 值是一个相当深的主题、函数序言和结尾。
- 参数需要由调用者放置在已知位置,并由被调用者从这些位置找到
- 固定的物理寄存器需要在调用者和被调用者之间共享,所以有一个协议可以实现这种共享。寄存器要么被调用保留,要么被调用破坏——每个组都适用于不同的场景,并且有自己的 rules/requirements 以便正常工作。
- 在汇编语言中可以有一个带有堆栈指针的显式调用堆栈,这在 C 代码中是看不到的。
- 可以有一个明确的 return 地址,它应该被认为是一个参数,被调用者使用它来 return 给正确的调用者(因为它可以动态地不同)。
- Return 值由被调用者放置在已知位置,并由调用者在 return.
处找到
- 保存调用保留寄存器,局部变量存储,例如对于数组,参数(包括 return 地址)和局部变量可以在函数调用中存在——所有这些东西都需要内存存储,通常以在堆栈上分配的一些 space 的形式(尽管有时在 LC-3 上这些是作为全局变量完成的,这意味着不支持递归)。如果堆栈 space 用于其中任何一个,则该 space 称为堆栈帧。
- 堆栈 space 在函数序言中分配并在函数尾声中释放——这些是函数代码前后函数体的部分(它们在每次函数调用时只执行一次,它们从不作为一部分即使整个函数体都是一个循环也是如此。
- 通过查找您正在使用的“调用约定”查看更多信息,它将描述寄存器共享组、专用寄存器(例如堆栈指针)以及参数和 return 值位置。
- 分析逻辑变量是否“在调用过程中存在”有助于选择合适的 CPU 寄存器,并告诉我们是否需要在序言中保存该寄存器并在尾声中恢复它。
有关详细信息,请参阅以下一些资源:
我了解了 LC-3 的工作原理,但我终生无法弄清楚如何在 LC-3 汇编中编码。我的目标是能够编写简单的程序,例如生成斐波那契数或对数组进行排序。
有人可以给我指点资源来学习这个吗?我精通 Python 和 Java,所以我很清楚这些问题背后的基本逻辑。
学习汇编语言有几个方面,汇编语言是处理器机器代码的人类可读版本。
其他语言基本上都是逻辑层面的,而机器码很大程度上是物理层面的
一方面,这尤其体现在存储概念上的差异:
- 逻辑变量与 CPU 物理寄存器和内存
- 逻辑变量是动态的,CPU寄存器和内存是固定的,永久的
- 具有类型的变量与具有位的物理存储
因此,当我们编写汇编语言时,我们翻译我们的伪代码:具有大量有限生命周期类型变量的逻辑代码,部分是通过将逻辑变量映射到固定的物理资源。通常有比 CPU 寄存器更多的变量,尤其是当某些寄存器有专用用途时,如堆栈或 return 地址。
- 逻辑变量与 CPU 物理寄存器和内存
另一方面,今天的其他语言通常采用结构化编程,而在汇编 language/machine 代码中我们有 if-goto-label。
所有结构化语句在 if-goto-label 中都有翻译。每个翻译都是将结构化形式的模式转换为 if-goto-label 形式的模式。正确地遵循这些模式,您将重现伪代码的控制流 — 在这里很容易走捷径并犯下令人困惑的错误,因此我鼓励在这里采用有条不紊的方法。
其他语言有丰富的表达式:有许多优先级的运算符,并且可以使用
()
的复杂程度。机器代码的指令(通常)最多需要 3 个操作数。函数调用、堆栈框架、参数传递、return 值是一个相当深的主题、函数序言和结尾。
- 参数需要由调用者放置在已知位置,并由被调用者从这些位置找到
- 固定的物理寄存器需要在调用者和被调用者之间共享,所以有一个协议可以实现这种共享。寄存器要么被调用保留,要么被调用破坏——每个组都适用于不同的场景,并且有自己的 rules/requirements 以便正常工作。
- 在汇编语言中可以有一个带有堆栈指针的显式调用堆栈,这在 C 代码中是看不到的。
- 可以有一个明确的 return 地址,它应该被认为是一个参数,被调用者使用它来 return 给正确的调用者(因为它可以动态地不同)。
- Return 值由被调用者放置在已知位置,并由调用者在 return. 处找到
- 保存调用保留寄存器,局部变量存储,例如对于数组,参数(包括 return 地址)和局部变量可以在函数调用中存在——所有这些东西都需要内存存储,通常以在堆栈上分配的一些 space 的形式(尽管有时在 LC-3 上这些是作为全局变量完成的,这意味着不支持递归)。如果堆栈 space 用于其中任何一个,则该 space 称为堆栈帧。
- 堆栈 space 在函数序言中分配并在函数尾声中释放——这些是函数代码前后函数体的部分(它们在每次函数调用时只执行一次,它们从不作为一部分即使整个函数体都是一个循环也是如此。
- 通过查找您正在使用的“调用约定”查看更多信息,它将描述寄存器共享组、专用寄存器(例如堆栈指针)以及参数和 return 值位置。
- 分析逻辑变量是否“在调用过程中存在”有助于选择合适的 CPU 寄存器,并告诉我们是否需要在序言中保存该寄存器并在尾声中恢复它。
有关详细信息,请参阅以下一些资源: