在装配中找到最大值和最小值

Posted

技术标签:

【中文标题】在装配中找到最大值和最小值【英文标题】:Finding max and min in assembly 【发布时间】:2013-09-23 00:48:22 【问题描述】:

尝试在汇编中执行以下操作:

#include <stdio.h>
main()
        int a,b,c=0,d=0;
        for(a = -3; a >= 3; a++)
                b = a*a*a*a*a*a*a - a*a*a*a*4 - a*a*a + a*a*7 + 30*a;
                if(b>=c)
                        c=b;
                
                if(b<=d)
                        d=b;
                
        
    printf("The max value is" + c + "\n");
    printf("The min value is" + d + "\n");

我在汇编中编写的代码不多,并且我希望在查看汇编语言时能够理解我的代码,所以简单地将其转换为使用 gcc 的汇编是不可能的。

我的目标是在编写此代码时尽可能少地使用nop。所以我想我的问题是:

    在没有大规模组装的情况下,最好的方法是什么? 重新进入登记簿的数量?是这样吗 可能吗? 当我在汇编中循环时,我是否想要已经设置a 的值?或者可以 我在这里编程有点像这样的for循环? (我可以避免while循环以节省空间吗)

【问题讨论】:

@Christian:你是对的,但请阅读散文。问题是询问如何在汇编中完成等价的事情,使用 C 作为他们想要发生的事情的模型。 @Christian:即使是家庭作业,他们也在问一个有效的问题,而不是要求为他们完成工作。 两个建议:1)关于 GCC/x86 程序集的优秀链接:Programming from the Ground Up, Jonathan Bartlett,2)“gcc -S”你的 C sn-ps 并分析结果。 “使用 gcc 将其转换为程序集”强调 NOT “有点问题”。这是推荐。恕我直言... @Ryan,我发现这个 link 很有趣,你可能想看看。 for (a = -3; a &gt;= 3; a++) -- 你认为这个循环多久执行一次? 【参考方案1】:

这段代码是编译器优化可以做什么的一个很好的例子。假设您的意思是for (a = -3; a &lt;= 3; a++) ...,那么您可以将其重新编码为:

#include <stdio.h>

#define val(a)  (a*a*a*a*a*a*a - a*a*a*a*4 - a*a*a + a*a*7 + 30*a)

#define MIN(X, Y)       ((X) > (Y) ? (Y) : (X))
#define MAX(X, Y)       ((X) > (Y) ? (X) : (Y))

int main(int argc, char **argv)

    int c = 0, d = 0, a;

    for (a = -3; a <= 3; a++)
            c = MAX(val(a), c), d = MIN(val(a), d);
    printf("max value is %d,\nmin value is %d\n", c, d);
    return 0;

英特尔的 ICC 直接把它变成:

 4006f9: [ ... 无意义的“ICC 粘合”指令 ... ]
  4006fd: bf 7c 0b 40 00 移动 $0x400b7c,%edi
  400702: 是 c5 07 00 00 mov $0x7c5,%esi
  400707:[ ...毫无意义的“ICC胶水”指令...]
  40070e: ba 31 f6 ff ff mov $0xfffff631,%edx
  400713: 33 c0 xor %eax,%eax
  400715:[ ...毫无意义的“ICC胶水”指令...]
  400719:e8 2a fe ff ff callq 400548 
  40071e: 33 c0 xor %eax,%eax
  400720: 48 89 ec 移动 %rbp,%rsp
  400723:5d 弹出 %rbp
  400724:c3 retq

CLang (3.3) 也可以,它创建:

0000000000400b60 
: 400b60: 50 推%rax 400b61: bf 6c 0c 40 00 移动 $0x400c6c,%edi 400b66: 是 c5 07 00 00 mov $0x7c5,%esi 400b6b: ba 31 f6 ff ff mov $0xfffff631,%edx 400b70: 30 c0 xor %al,%al 400b72: e8 39 fd ff ff callq 4008b0 400b77: 31 c0 xor %eax,%eax 400b79: 5a 弹出 %rdx 400b7a: c3 retq

在这种情况下,GCC(直到并包括 4.8.1)似乎无法进行编译时计算,在展开循环时,它会插入一系列乘法、条件移动/SSE 最小/最大指令。

但是,如果您在代码中手动显式展开循环,则会得到:

c = MAX(val(-3), 0); d = MIN(val(-3), 0);
c = MAX(val(-2), c); d = MIN(val(-2), d);
c = MAX(val(-1), c); d = MIN(val(-1), d);
c = MAX(val(0), c); d = MIN(val(0), d);
c = MAX(val(1), c); d = MIN(val(1), d);
c = MAX(val(2), c); d = MIN(val(2), d);
c = MAX(val(3), c); d = MIN(val(3), d);

并且 GCC 能够在编译时计算它:

0000000000400630 
: 400630: 48 83 ec 08 sub $0x8,%rsp 400634: ba 31 f6 ff ff mov $0xfffff631,%edx 400639: 是 c5 07 00 00 mov $0x7c5,%esi 40063e: bf 50 07 40 00 移动 $0x400750,%edi 400643: 31 c0 xor %eax,%eax 400645:e8 79 fe ff ff callq 4004c0 40064a: 31 c0 xor %eax,%eax 40064c: 48 83 c4 08 添加 $0x8,%rsp 400650: c3 retq

士气:在这种情况下,最好的结果是而不是尝试优化汇编器输出:)

【讨论】:

我应该补充一下,只是为了明确一点,这个特定的源代码有一个可分析计算的结果,即你可以计算出c/@的值987654325@ 最后使用“铅笔和纸”。这就是编译器在这里所​​做的。无论这是否错过了分配的重点,这都是这种情况下的最佳结果。如果您想了解编译器如何表达最小/最大值(在 x86 上,三个选项:“经典”cmpj...cmp 和条件移动以及 SSE 最小/最大值指令),请调整范围边界起来。

以上是关于在装配中找到最大值和最小值的主要内容,如果未能解决你的问题,请参考以下文章

如何在while循环中找到最大值和最小值 - c

如何在数组java中找到最大值和最小值?

熊猫找到局部最大值和最小值,而不是高原值

如何在文件处理中从文本文件中找到每一行的最小值和最大值?

如何在二叉树中找到最小值和最大值

如何找到最大值和最小值[重复]