在装配中找到最大值和最小值
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 >= 3; a++)
-- 你认为这个循环多久执行一次?
【参考方案1】:
这段代码是编译器优化可以做什么的一个很好的例子。假设您的意思是for (a = -3; a <= 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 40054840071e: 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 上,三个选项:“经典”cmp
和 j...
、cmp
和条件移动以及 SSE 最小/最大值指令),请调整范围边界起来。以上是关于在装配中找到最大值和最小值的主要内容,如果未能解决你的问题,请参考以下文章