热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《为什么GCC没有为intdivision生成正确的汇编代码?》经验,为你挑选了2个好方法。

我写了以下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).

另外,在跳转到反汇编之前,我建议查看预处理源,可以使用cppgcc -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).

另外,在跳转到反汇编之前,我建议查看预处理源,可以使用cppgcc -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 ..
推荐阅读
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • 本文介绍了GTK+中的GObject对象系统,该系统是基于GLib和C语言完成的面向对象的框架,提供了灵活、可扩展且易于映射到其他语言的特性。其中最重要的是GType,它是GLib运行时类型认证和管理系统的基础,通过注册和管理基本数据类型、用户定义对象和界面类型来实现对象的继承。文章详细解释了GObject系统中对象的三个部分:唯一的ID标识、类结构和实例结构。 ... [详细]
  • OpenMap教程4 – 图层概述
    本文介绍了OpenMap教程4中关于地图图层的内容,包括将ShapeLayer添加到MapBean中的方法,OpenMap支持的图层类型以及使用BufferedLayer创建图像的MapBean。此外,还介绍了Layer背景标志的作用和OMGraphicHandlerLayer的基础层类。 ... [详细]
  • linux进阶50——无锁CAS
    1.概念比较并交换(compareandswap,CAS),是原⼦操作的⼀种,可⽤于在多线程编程中实现不被打断的数据交换操作࿰ ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • 本文介绍了Swing组件的用法,重点讲解了图标接口的定义和创建方法。图标接口用来将图标与各种组件相关联,可以是简单的绘画或使用磁盘上的GIF格式图像。文章详细介绍了图标接口的属性和绘制方法,并给出了一个菱形图标的实现示例。该示例可以配置图标的尺寸、颜色和填充状态。 ... [详细]
  • CentOS7.8下编译muduo库找不到Boost库报错的解决方法
    本文介绍了在CentOS7.8下编译muduo库时出现找不到Boost库报错的问题,并提供了解决方法。文章详细介绍了从Github上下载muduo和muduo-tutorial源代码的步骤,并指导如何编译muduo库。最后,作者提供了陈硕老师的Github链接和muduo库的简介。 ... [详细]
  • 本文讨论了如何使用GStreamer来删除H264格式视频文件中的中间部分,而不需要进行重编码。作者提出了使用gst_element_seek(...)函数来实现这个目标的思路,并提到遇到了一个解决不了的BUG。文章还列举了8个解决方案,希望能够得到更好的思路。 ... [详细]
  • 使用freemaker生成Java代码的步骤及示例代码
    本文介绍了使用freemaker这个jar包生成Java代码的步骤,通过提前编辑好的模板,可以避免写重复代码。首先需要在springboot的pom.xml文件中加入freemaker的依赖包。然后编写模板,定义要生成的Java类的属性和方法。最后编写生成代码的类,通过加载模板文件和数据模型,生成Java代码文件。本文提供了示例代码,并展示了文件目录结构。 ... [详细]
author-avatar
青春梦敲门砖
我滴妈呀!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有