【问题标题】:finding average, min and max in assembly在装配中找到平均值、最小值和最大值
【发布时间】:2017-03-08 07:53:34
【问题描述】:

我在用汇编语言查找数组的平均值、最小值和最大值时遇到问题。我用 C++ 创建了一个简单的数组,并创建了一个 test.asm 文件来传递它。我算出了平均值,但现在我似乎无法算出最小值和最大值。

#include <iostream>
using namespace std;

extern "C"
int test(int*, int);

int main()
{
const int SIZE = 7;
int arr[SIZE] = { 1,2,3,4,5,6,7 };

int val = test(arr, SIZE);


cout << "The function test returned: " << val << endl;

return 0;
}

这是我的 test.asm,它将所有值相加并返回 4。

.686
.model flat

.code


_test PROC ;named _test because C automatically prepends an underscode, it is needed to interoperate
push ebp
mov ebp,esp ;stack pointer to ebp

mov ebx,[ebp+8] ; address of first array element
mov ecx,[ebp+12]
mov ebp,0
mov edx,0
mov eax,0


loopMe:
cmp ebp,ecx
je allDone


add eax,[ebx+edx]
add edx,4
add ebp,1
jmp loopMe

allDone:
mov edx,0
div ecx

pop ebp
ret
_test ENDP

END

我仍在试图找出如何找到最小值,因为最大值将以类似的方式完成。我假设您使用 cmp 来比较值,但到目前为止我尝试的一切都没有成功。我对汇编语言还很陌生,我很难掌握。任何帮助表示赞赏。

【问题讨论】:

  • 建议改用 C 语言编写它,并查看编译器输出以了解它的作用。然后决定你是否真的想用汇编语言编写。
  • 选择一个寄存器来保存你的最小值/最大值,并用第一个数组元素初始化它。然后使用cmp 指令来确定是否将该值替换为当前元素。很简单。
  • 您想将最小值、最大值和平均值返回给调用进程吗?可以做到这一点,只要一个 32 位寄存器中的值小于 255,但大于该值的值需要额外的逻辑。
  • 显示您的“最小”尝试失败的原因是什么?此外,还不清楚您希望如何返回 3 个值(返回单个 int)。而且我认为最好将这个平均函数清理到你能做的最好,验证它是否有效,并将其发布到 codereview 站点以获取建议,因为这可以写得更“干净”一点,使用更少的寄存器和说明(阅读和调试更容易)。也许您要问的一种方法是显示注释良好(高级意图)的简短 asm 源,并指出什么值/inst。你不明白,或者高层的哪一部分你写不出来。
  • 顺便说一句,我什至更喜欢测试项目以比在空数组上“除以零时崩溃”更好的方式设计 API。再添加一个 test+return 值只会增加一点点工作量,即使只进行一些“学习”测试,您也应该始终考虑 API 的使用情况。

标签: c++ arrays assembly max min


【解决方案1】:

感谢任何帮助

好的,所以我将向您展示重构的平均函数,即使您没有直接要求它。 :)

你可以从中学到的东西:

  • ebp未在代码中修改时,简化功能序言/尾声
  • 输入数组是 32b int 值,因此要获得正确的平均值,您应该计算 64b 总和,并进行 64b 总和有符号除法
  • 微妙的“技巧”如何获得零值 (xor) 或 inc 如何为值 +1(降低代码大小)
  • 通过返回虚假平均值 0 处理零大小数组(无崩溃)
  • 由 32b 寄存器/指令组成的两个 64b 值相加
  • 计算人类“索引”(+1 => 直接 cmp 与 size 可能),但寻址 32b 值(在寻址中使用 *4
  • 重命名为getAverage

顺便说一句,这并没有针对性能进行优化,我试图让源代码保持“简单”,所以它很容易阅读和理解它在做什么。

_getAverage PROC
    ; avoiding `ebp` usage, so no need to save/set it
    mov   ebx,[esp+4]   ; address of first array element
    mov   ecx,[esp+8]   ; size of array
    xor   esi,esi       ; array index 0
    ; 64b sum (edx:eax) = 0
    xor   eax,eax
    cdq
    ; test for invalid input (zero sized array)
    jecxz zeroSizeArray ; arguments validation, returns 0 for 0 size

    ; here "0 < size", so no "index < size" test needed for first element
    ; "do { ... } while(index < size);" loop variant

sumLoop:
    ; extend value from array[esi] to 64b (edi is upper 32b)
    mov   edi,[ebx+esi*4]
    sar   edi,31
    ; edx:eax += edi:array[esi]  (64b array value added to 64b sum)
    add   eax,[ebx+esi*4]
    adc   edx,edi
    ; next index and loop while index < size
    inc   esi
    cmp   esi,ecx
    jb    sumLoop

    ; divide the 64b sum of integers by "size" to get average value
    idiv  ecx           ; signed (!) division (input array is signed "int")
    ; can't overflow (Divide-error), as the sum value was accumulated
    ; from 32b values only, so EAX contains full correct result
zeroSizeArray:
    ret
_getAverage ENDP

【讨论】:

  • 很好,很好的想法,你可以使用 idiv 而没有溢出的风险。我可能在循环中使用了 CDQ(将每个数组元素仅加载一次到 EAX 中),但这需要循环后的两条 MOV 指令。对同一个位置的两个内存引用在这里还不错(我认为这不会损害可读性或可理解性,这就是你想要的)。当然,如果我关心速度,我会在 SSE4 PMOVZX 之后使用 PADDQ,或者使用 SSE2 手动进行符号扩展(可能是原始的 PSRAD 和 PUNPCKL/HDQ)。
猜你喜欢
  • 2016-12-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-05
  • 1970-01-01
  • 2015-01-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多