我写了以下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]));
}
文件: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 : mov 0x8(%ebp),%eax
0x80001c40 : mov 0x4(%eax),%eax
0x80001c43 : shr $0x2,%eax
0x80001c46 : mov $0x24924925,%edx
0x80001c4b : mul %edx
0x80001c4d : mov %edx,%eax
0x80001c4f : shl $0x2,%eax
0x80001c52 : add %edx,%eax
0x80001c54 : add %eax,%eax
0x80001c56 : add $0x1,%eax
0x80001c59 : mov %eax,-0x2c(%ebp)
0x80001c5c : mov 0x8(%ebp),%eax
0x80001c5f : mov 0x4(%eax),%eax
0x80001c62 : 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 : mov %eax,%ecx
0x800005dd : mov %edi,%eax
0x800005df : cltd
0x800005e0 : idiv %ecx
0x800005e2 : mov %eax,-0x1c(%ebp)
zneak..
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))
这也会在整个表达式周围添加括号是安全的,因为否则会遇到类似的问题foo * E(bar, baz)
.
另外,在跳转到反汇编之前,我建议查看预处理源,可以使用cpp
或gcc -E
(或clang -E
)来完成.
1> zneak..:
仅仅因为你没有看到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))
这也会在整个表达式周围添加括号是安全的,因为否则会遇到类似的问题foo * E(bar, baz)
.
另外,在跳转到反汇编之前,我建议查看预处理源,可以使用cpp
或gcc -E
(或clang -E
)来完成.
至少显示预期和实际的输入和输出.
@zneak - bs的定义也应该用括号括起来,以防它在另一个表达式中使用:`#define bs(sizeof(D_Record)*MAX_BLOCK_ENTRIES)`.
2> rcgldr..:
最初的问题了
#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和乘法除以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
链接到先前的线程,解释如何将除以常数转换为乘法和移位.
为什么GCC在实现整数除法时使用乘以奇数的乘法?
实际上,这甚至会除以280吗?看起来像28对我来说.紧随其后的神秘乘法是10 ..