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

Kar*_*uil -2 c assembly gcc integer

我写了以下C代码.但是在运行时它给了我不正确的变量值sb,所以我尝试用GDB调试它,我发现int division 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]));
    }
Run Code Online (Sandbox Code Playgroud)

文件: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);

}
Run Code Online (Sandbox Code Playgroud)

为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
Run Code Online (Sandbox Code Playgroud)

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

文件: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;
}
Run Code Online (Sandbox Code Playgroud)

为以前的代码生成的汇编代码(这是一个很容易理解和正确执行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)
Run Code Online (Sandbox Code Playgroud)

zne*_*eak 10

仅仅因为你没有看到idiv指令或乍一看你无法理解代码并不意味着它是不正确的.编译器通过乘以一个大因子然后除以二次幂来按常数优化除法.


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

#define E(X, Y) ((X)/(Y))
Run Code Online (Sandbox Code Playgroud)

这也会在整个表达式周围添加括号是安全的,因为否则会遇到类似的问题foo * E(bar, baz).

另外,在跳转到反汇编之前,我建议查看预处理源,可以使用cppgcc -E(或clang -E)来完成.

  • 至少显示预期和实际的输入和输出. (2认同)
  • @afr0ck,这不是预处理器的错。预处理器不知道任何句法含义。编写像`#define E(X) X /`这样的宏是完全合法的,你会像`E(4) 2`一样使用它。 (2认同)
  • @zneak - bs的定义也应该用括号括起来,以防它在另一个表达式中使用:`#define bs(sizeof(D_Record)*MAX_BLOCK_ENTRIES)`. (2认同)

rcg*_*ldr 7

最初的问题了

#define bs      280
Run Code Online (Sandbox Code Playgroud)

后来改为:

#define bs      sizeof(D_Record)*MAX_BLOCK_ENTRIES
Run Code Online (Sandbox Code Playgroud)

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

#define bs      (sizeof(D_Record)*MAX_BLOCK_ENTRIES)
Run Code Online (Sandbox Code Playgroud)

E的定义应该是:

#define E(X,Y) ((X)/(Y))
Run Code Online (Sandbox Code Playgroud)

生成的汇编代码似乎基于

#define bs      sizeof(D_Record)*MAX_BLOCK_ENTRIES
#define E(X,Y) X/Y
    ... E(vdes->addr, bs) ...
Run Code Online (Sandbox Code Playgroud)

因此,使用shift和乘法除以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
Run Code Online (Sandbox Code Playgroud)

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

        lea    (%edx,%edx,2),eax    ;eax = edx*5
        add    %eax,%eax            ;eax = edx*10
Run Code Online (Sandbox Code Playgroud)

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

为什么GCC在实现整数除法时使用乘以奇数的乘法?

  • 实际上,这甚至会除以280吗?看起来像28对我来说.紧随其后的神秘乘法是10 .. (2认同)