哇哦,SPARC 汇编语言,我好几年没见过了。
我想我们一行一行地走?我将跳过一些无趣的样板。
.section ".rodata"
.align 8
.LLC0:
.asciz "%d\n"
这是您在其中使用的字符串常量printf
(很明显,我知道!)要注意的重要事项是它在.rodata
节中(节是最终可执行映像的分区;这是用于“只读数据”的,将在事实上在运行时是不可变的)并且它被赋予了标签 .LLC0
。以点开头的标签是对象文件的私有标签。稍后,编译器在要加载字符串常量的地址时会引用该标签。
.section ".text"
.align 4
.global main
.type main, #function
.proc 020
main:
.text
是实际机器代码的部分。这是用于定义名为 的全局函数的样板头文件main
,它在程序集级别与任何其他函数没有什么不同(在 C 中 - 在 C++ 中不一定如此)。我不记得是什么.proc 020
。
save %sp, -112, %sp
保存上一个寄存器窗口,向下调整堆栈指针。如果您不知道注册窗口是什么,您需要阅读架构手册:http ://sparc.org/wp-content/uploads/2014/01/v8.pdf.gz 。(V8 是 SPARC 的最后一个 32 位迭代,V9 是第一个 64 位迭代。这似乎是 32 位代码。)
sethi %hi(.LLC0), %g1
or %g1, %lo(.LLC0), %o0
.LLC0
这两个指令序列具有将地址(即您的字符串常量)加载到 register 的净效果%o0
,这是第一个传出参数寄存器。(这个函数的参数在传入的参数寄存器中。)
mov 20, %o1
将立即数 100 加载到%o1
第二个传出参数寄存器中。这是由 计算的值((foo *)0)+1
。它是 20,因为您的struct foo
长度为 20 个字节(五个 4 字节int
),并且您要求数组中从地址 0 开始的第二个字节。
顺便说一句,只有当基指针的地址处实际上有一个足够大的数组时,才在 C 中明确定义了从指针计算偏移量。((foo *)0)
是一个空指针,所以那里没有数组,所以表达式在((foo *)0)+1
技术上具有未定义的行为。针对托管 SPARC 的 GCC 4.2.1 恰好将其解释为“假设foo
在地址 0 处有一个任意大的 s 数组并计算数组成员 1 的预期偏移量”,但其他(尤其是较新的)编译器可能会完全执行某些操作不同的。
call printf, 0
nop
打电话printf
。我不记得零是干什么用的。该call
指令有一个延迟槽(再次阅读架构手册),其中填充了一个无操作指令,nop
.
return %i7+8
nop
跳转到寄存器中的地址%i7
加八。这具有从当前函数返回的效果。
return
还有一个延迟槽,里面是另一个nop
. restore
在这个延迟槽中应该有一条指令,匹配save
函数顶部的 ,以便main
调用者取回它的寄存器窗口。我不知道为什么它不在那里。评论中的讨论讨论了main
可能不需要弹出注册窗口,和/或您已声明main
为void main()
(不能保证与任何 C 实现一起使用,除非其文档明确说明,并且总是不好的风格)......但是在 SPARC 上推送而不弹出注册窗口是一件很麻烦的事情,我认为这两种解释都不能令人信服。我什至可以称它为编译器错误。