微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何理解这个基本的汇编代码?

如何解决如何理解这个基本的汇编代码?

根据以下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 的补码机,因此 adduadd 是相同的二元运算,不同之处仅在于不捕获有符号溢出:当输入为相同的符号,但输出的符号与此不同。)


就 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_tintint32_t 数组。

我选择整数是因为当你做指针加法时,C 指针类型会神奇地按类型宽度进行缩放。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?