为什么GCC没有为int division生成正确的汇编代码?

时间:2017-06-29 23:03:35

标签: c gcc assembly integer

我写了以下C代码。但是在运行时它为变量sb提供了不正确的值,所以我尝试用GDB调试它,我发现int除E(vdes->addr, bs)#define E(X,Y) X/Y)的汇编代码是完全不可理解,似乎没有做正确的事。

文件:main.c

typedef struct virtual_file_descriptor
{
    int     dfb;
    int     addr;

} vfd;

vfd vdes;

if(!strcmp(argv[1], "write")){
        vdes.dfb = atoi(argv[2]);
        vdes.addr = atoi(argv[3]);
        vwrite(&vdes, inbuffer, atoi(argv[4]));
    }

文件:vwrite.c

#define E(X,Y)  X/Y
#define bs      sizeof(D_Record)*MAX_BLOCK_ENTRIES

int vwrite(vfd *vdes, char *buffer, int size){
    if(!vdes)
        return -1;

    int sb, nb, offset;

    sb      = E(vdes->addr, bs) + 1;     // i did 140/280 => wrong result
    offset  = vdes->addr - (sb - 1) * bs;

    printf("size=%d bs=%d   addr=%d sb=%d   offset=%d\n\n", size, bs, vdes->addr, sb, offset);

}

为int devision生成的汇编语言是(这是错误的,并且不包含任何进行算术划分的声音):

(gdb) n
58      sb      = E(vdes->addr, bs) + 1;
(gdb) x/10i $pc
=> 0x80001c3d <vwrite+39>:  mov    0x8(%ebp),%eax
   0x80001c40 <vwrite+42>:  mov    0x4(%eax),%eax
   0x80001c43 <vwrite+45>:  shr    $0x2,%eax
   0x80001c46 <vwrite+48>:  mov    $0x24924925,%edx
   0x80001c4b <vwrite+53>:  mul    %edx
   0x80001c4d <vwrite+55>:  mov    %edx,%eax
   0x80001c4f <vwrite+57>:  shl    $0x2,%eax
   0x80001c52 <vwrite+60>:  add    %edx,%eax
   0x80001c54 <vwrite+62>:  add    %eax,%eax
   0x80001c56 <vwrite+64>:  add    $0x1,%eax
   0x80001c59 <vwrite+67>:  mov    %eax,-0x2c(%ebp)
   0x80001c5c <vwrite+70>:  mov    0x8(%ebp),%eax
   0x80001c5f <vwrite+73>:  mov    0x4(%eax),%eax
   0x80001c62 <vwrite+76>:  mov    %eax,%edx

我将相同的代码序列复制到一个新的独立文件中,一切正常(正确的结果和正确的汇编代码)。所以我想知道为什么第一个代码不起作用?

文件:test.c

#define E(X,Y) X/Y

int main(int argc, char **argv){

    int sb = E(atoi(argv[1]), atoi(argv[2]));

    return 0;
}

为以前的代码生成的汇编代码(这是一个非常容易理解和正确的代码,用于执行int devision):

   .
   .
   call atoi
   .
   call atoi
   .
   .
   0x800005db <main+75>:    mov    %eax,%ecx
   0x800005dd <main+77>:    mov    %edi,%eax
   0x800005df <main+79>:    cltd   
   0x800005e0 <main+80>:    idiv   %ecx
   0x800005e2 <main+82>:    mov    %eax,-0x1c(%ebp)

2 个答案:

答案 0 :(得分:10)

仅仅因为你没有看到sizeof(D_Record)*MAX_BLOCK_ENTRIES指令或乍一看你无法理解代码并不意味着它是不正确的。编译器optimize divisions by constants乘以一个较大的因子,然后除以2的幂。

根据评论中的其他信息,我们知道bs过去被定义为E(vdes->addr, bs)。该错误是vfs->address / sizeof(D_Record) * MAX_BLOCK_ENTRIES宏扩展为(vfs->address / sizeof(D_Record)) * MAX_BLOCK_ENTRIES,相当于E。解决方案是在#define E(X, Y) ((X)/(Y)) 的定义中添加括号以正确分组:

foo * E(bar, baz)

这也会在整个表达式周围添加括号以确保安全,因为否则您可能会遇到cpp的类似问题。

此外,在跳转到反汇编之前,我建议您查看预处理来源,可以使用gcc -Eclang -E(或 $userService = $this->container->get('userservice'); $users = $userService->findAll(); foreach ($users as $user){ $usersWPoints = $user->getSubmissions()->getProblemId()->getPoints; } )来完成。

答案 1 :(得分:7)

最初的问题已经

#define bs      280

后来改为:

#define bs      sizeof(D_Record)*MAX_BLOCK_ENTRIES

为了避免在其他表达式中使用bs的问题,这应该是

#define bs      (sizeof(D_Record)*MAX_BLOCK_ENTRIES)

E的定义应为:

#define E(X,Y) ((X)/(Y))

生成的汇编代码似乎基于

#define bs      sizeof(D_Record)*MAX_BLOCK_ENTRIES
#define E(X,Y) X/Y
    ... E(vdes->addr, bs) ...

因此,使用shift和multiply除以28,然后乘以10.

        mov    0x4(%eax),%eax       ;eax = dividend
        shr    $0x2,%eax            ;eax = dividend/4 (pre shift)
        mov    $0x24924925,%edx     ;edx = multiply constant
        mul    %edx                 ;edx = dividend/28 (no post shift)
        mov    %edx,%eax            ;eax = (dividend/28)*10
        shl    $0x2,%eax
        add    %edx,%eax
        add    %eax,%eax

对于eax = edx * 10序列,我不确定为什么没有使用lea:

        lea    (%edx,%edx,2),eax    ;eax = edx*5
        add    %eax,%eax            ;eax = edx*10

链接到先前的线程,并解释如何将除以常数转换为乘法和移位。

Why does GCC use multiplication by a strange number in implementing integer division?