25

我目前正在编写一个简单的 C 编译器,它将 .c 文件作为输入并生成汇编代码(X86、AT&T 语法)。一切都很好,但是当我尝试执行 IDIVQ 指令时,我得到了一个浮点异常。这是我的输入:

int mymain(int x){
  int d;
  int e;
  d = 3;
  e = 6 / d;
  return e;
}

这是我生成的代码:

mymain:
.LFB1:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    movq    %rsp, %rbp
    .cfi_offset 6, -16
    .cfi_def_cfa_register 6
    movq    %rdi, -40(%rbp)
    movq    $3, -8(%rbp)
    movq    $6, %rax
    movq    -8(%rbp), %rdx
    movq    %rdx, %rbx
    idivq   %rbx
    movq    %rax, -16(%rbp)
    movq    -16(%rbp), %rax
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE1:
    .size mymain, .-mymain

根据http://www.cs.virginia.edu/~evans/cs216/guides/x86.htmlidivq %rbx应该在%rax中产生 6/d (商)。但是我遇到了一个浮点异常,我似乎找不到问题所在。

任何帮助都感激不尽!

4

2 回答 2

26

Mysticials 答案的第一部分是正确的,idiv进行 128/64 位除法,所以 的值rdx,它保存被除数的高 64 位,不能包含随机值。但是零扩展是错误的方法。

由于您已经签署了变量,因此您需要签署extend raxto rdx:rax。在 AT&T 和Intel 语法中有一个特定的指令cqto将 quad 转换为 oct )。cqoAFAIK 较新版本的气体接受这两个名称。

movq    %rdx, %rbx
cqto                  # sign extend rax to rdx:rax
idivq   %rbx
于 2012-04-27T10:23:57.923 回答
13

idivq指令将一个 128 位整数 ( rdx:rax) 除以给定的源操作数。

  • rax持有被除数的低 64 位。
  • rdx持有被除数的高 64 位。

当商不适合 64 位时,idiv将出错(#DE 异常,操作系统通过提供POSIX 要求的算术异常所需的 SIGFPE 信号来处理该异常)。

由于您正在编译使用 signed 的代码int,因此您还需要对 extend raxto进行签名rdx:rax,这意味着将rax符号位复制到的每一位,rdx并使用 cqo 别名 cqto 完成:

movq    %rdx, %rbx        # or load into RBX or RCX in the first place
cqo
idivq   %rbx              # signed division of RDX:RAX / RBX

如果您一直在进行unsigned除法,则将 RDX 归零以将 RAX 零扩展为 RDX:RAX:

movq    %rdx, %rbx
xor     %edx, %edx      # zero "rdx"
divq    %rbx            # unsigned division of RDX:RAX / RBX

另请注意,在 x86-64 System V ABI 中,int是 32 位有符号类型,而不是 64 位。在这种情况下,将其扩大到 64 位是合法的(因为结果相同),但会使您的代码变慢,尤其是除法。

于 2012-04-27T00:27:45.197 回答