使用IMUL指令将数组中的值相乘会产生不正确的值

时间:2018-03-11 17:04:35

标签: c ubuntu gcc assembly x86

我正在学习ASM语言并在Ubuntu Eclipse C ++上试用IMUL函数,但出于某种原因我似乎无法从我的代码中获得所需的输出。

  

必需:

     

将整数数组int_array的负数元素乘以指定的整数inum

这是我的上述代码:

C代码:

#include <stdio.h>
extern void multiply_function();

// Variables
int iaver, inum;
int int_ar[10] = {1,2,3,4,-9,6,7,8,9,10};

int main()
{
    inum = 2;
    multiply_function();    
    for(int i=0; i<10; i++){
        printf("%d ",int_ar[i]);
    }
}

ASM代码:

extern int_ar
extern inum
global multiply_function

multiply_function:

    enter 0,0
    mov ecx, 10
    mov eax, inum

multiply_loop:
    cmp [int_ar +ecx*4-4], dword 0 
    jg .ifpositive 
    mov ebx, [int_ar +ecx*4-4]
    imul ebx
    cdq
    mov [int_ar +ecx*4-4], eax
    loop multiply_loop
    leave
    ret

.ifpositive:
    loop multiply_loop
    leave
    ret

问题

对于{1,2,3,4,-9,6,7,8,9,10}inum的数组,我得到输出{1,2,3,4,-1210688460,6,7,8,9,10},暗示出现某种溢出。

对于x86汇编语言中的IMUL函数是如何工作的,我是否遗漏或理解错误?

预期产出

我预期的输出是{1,2,3,4,-18,6,7,8,9,10}

我的思维过程

我对上述任务的思考过程:

1)查找数组中哪些数组元素为负数,对于找到的每个正元素,不执行任何操作并继续循环到下一个元素

 cmp [int_ar +ecx*4-4], dword 0 
 jg .ifpositive 

 .ifpositive:
    loop multiply_loop
    leave
    ret

2)找到负面元素后,将其值移动到寄存器EBX中,该寄存器EBX将作为IMUL SRC功能中的SRC。然后将寄存器EAX扩展到EAX-EDX,其结果存储在:

mov ebx, [int_ar +ecx*4-4]
    imul ebx
    cdq

3)使用MOV:

将结果移动到数组的负元素中
mov [int_ar +ecx*4-4], eax

4)循环到下一个数组元素并重复上述1)-3)

1 个答案:

答案 0 :(得分:4)

值不正确的原因

如果我们回顾一下效率低下和不需要的代码并处理真正的问题,那就归结为这条指令:

mov eax, inum

什么是inum?您在 C 中创建并初始化了一个名为inum的全局变量,其中包含:

int iaver, inum;
[snip]
inum = 2;

inum作为变量本质上是包含int(32位值)的内存位置的标签。在汇编代码中,您需要将inum视为指向值的指针,而不是值本身。在汇编代码中,您需要更改:

mov eax, inum

为:

mov eax, [inum]

您的版本所做的是将inum的地址移至 EAX 。您的代码最终将变量的地址乘以数组中的负数。这会导致你看到错误的价值观。 inum周围的方括号告诉汇编器要将inum视为内存操作数,并且要将inum处的32位值移动到 EAX

召集会议

您似乎正在创建一个32位程序并在32位Ubuntu上运行它。我可以通过返回错误值-1210688460来推断32位Linux的可能性。 -1210688460 = 0xB7D65C34除以-9,得到804A06C。 32位Linux上的程序通常从0x8048000开始加载

无论是在32位Linux还是64位Linux上运行,与32位C / C ++程序链接的汇编代码都需要遵守CDECL calling convention

  

的cdecl

     

cdecl(代表C声明)是一种调用约定,它源自C编程语言,并被许多C编译器用于x86架构。1在cdecl中,子例程参数在栈上传递。整数值和存储器地址在EAX寄存器中返回,ST0 x87寄存器中的浮点值。 注册表EAX,ECX和EDX被呼叫者保存,其余部分被呼叫者保存。调用新函数时,x87浮点寄存器ST0至ST7必须为空(弹出或释放),退出函数时ST1至ST7必须为空。不用于返回值时,ST0也必须为空。

您的代码包含 EAX EBX ECX EDX 。您可以自由销毁 EAX ECX EDX 的内容,但必须保留EBX 。如果您不这样做,可能会导致调用该函数的 C 代码出现问题。执行enter 0,0指示后,您可以push ebx在每次leave指令之前pop ebx

如果您要使用-O1-O2-O3 GCC 编译器选项来启用优化,您的程序可能按预期工作或完全崩溃。