如何解决如何理解这个基本的汇编代码?
根据以下MIPS指令构造C语言语句。
(var f-> $ s0,数组A和B的起始地址-> $ s6,$ s7)
addi $t0,$s6,4 //$t0 = &A[1]
add $t1,$0 //$t1 = &A[0]
sw $t1,0($t0) //A[1] = &A[0]
lw $t0,0($t0) //$t0 = &A[0]
add $s0,$t1,$t0 //f = &A[0] + &A[0]
左侧是给出的说明,而右侧的注释让我难以理解。
我得到的最终答案是f = &A[0] + &A[0]
,但这似乎不正确。我怎么了?
解决方法
你没疯,代码真的有那么奇怪!
添加两个指针基本上没有意义,所以这是一个棘手的问题。
等效的 C 看起来确实是错误的/疯狂的:
intptr_t *A = ...; // in $s6
A[1] = (intptr_t)&A[0];
f = A[1] + (intptr_t)&A[0];
请注意,有符号溢出在 C 中是未定义的行为,因此将其编译为 MIPS add
是合法的,它会在有符号溢出时捕获。如果我们使用 uintptr_t
,所需的溢出语义将是换行/截断,而 add
没有实现。
(MIPS 的真实世界 C 编译器总是使用 addu
/ addiu
,而不是 add
,即使对于有符号的 int,因为未定义的行为意味着任何事情都是允许的,包括包装。它甚至如果使用 gcc -fwrapv
编译,则需要。由于 MIPS 是 2 的补码机,因此 addu
与 add
是相同的二元运算,不同之处仅在于不捕获有符号溢出:当输入为相同的符号,但输出的符号与此不同。)
就 C 而言,它将编译回更接近给定 asm 的东西,或者至少用 C 临时变量表示每个 asm 操作:
我使用 GNU C register-global variables 而不是函数 args,因此函数体将使用实际正确的寄存器(并且不会用额外的指令来使 asm 混乱以保存/恢复和初始化这些寄存器)。所以这让我可以让 GCC 制作一个具有 s
寄存器作为输入和输出的 asm 块,而不是正常的调用约定。
#include <stdint.h>
register intptr_t *A asm("s6");
// register char *B asm("s7"); // unused,no idea what type makes sense
register intptr_t f asm("s0");
void foo()
{
volatile intptr_t *t0_ptr = A+1; // volatile forces store and reload
intptr_t t1 = (intptr_t)A;
*t0_ptr = t1; //sw $t1,0($t0) //A[1] = &A[0]
intptr_t t0_int = *t0_ptr; //lw $t0,0($t0) //$t0 = &A[0]
f = t0_int + t1; //add $s0,$t1,$t0 //f = &A[0] + &A[0]
//return f;
}
请注意,$t0
在这里用于 2 种不同的事物,具有不同的类型:一种是指向数组的指针,另一种是数组中的值。我用两个不同的 C 变量表达了这一点,因为事情通常是这样进行的。 (当一个变量在 / 之前“死”时,编译器会为不同的变量重用相同的寄存器,因为需要另一个变量。)
来自 GCC5.4 for MIPS 的结果汇编,带有 options to make MARS-compatible asm:-O2 -march=mips3 -fno-delayed-branch
。 MIPS3 意味着没有加载延迟槽,就像问题中的代码在加载后的指令中使用 lw
结果一样。 (Godbolt compiler explorer)
foo:
move $2,$22 # $v0,$s6 pointless copy into $v0
sw $22,4($2) # A[1] = A
lw $3,4($22) # v1 = A[1]
addu $16,$22,$3 # $s6 = (intptr_t)A + A[1]
j $31
nop # branch-delay slot
(GCC 使用数字寄存器名称,而不是 ABI 名称,例如 $s?
表示调用保留,$t?
表示调用破坏临时寄存器等。http://www.cs.uwm.edu/classes/cs315/Bacon/Lecture/HTML/ch05s03.html 有一个表。)
另一种不那么严谨的编写方式:重要的区别是缺少 volatile
来强制编译器重新加载。
void bar() {
A[1] = &A[0];
f = A[1] + (intptr_t)&A[0];
}
bar:
move $2,$22 # still a useless copy
sw $22,4($2)
sll $16,1 # 2 * (intptr_t)A; no reload,just CSE the store value.
j $31
nop
当然还有其他表达方式,例如使用 A
作为指针数组而不是 intptr_t
、int
或 int32_t
数组。
我选择整数是因为当你做指针加法时,C 指针类型会神奇地按类型宽度进行缩放。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。