Неправильный результат, загруженный из стека FPU после загрузки его со значением из массива

Я пишу встроенный ассемблерный код x86, чтобы скопировать содержимое массива, определенного на языке C, в стек x87 FPU для выполнения дальнейших операций. Значение, которое я сохраняю в верхней части стека FPU, отличается, когда я извлекаю его из стека FPU.

Я попытался посмотреть дизассемблированный код, сгенерированный компилятором; ссылка на руководство по Turbo C++ 3.0; обратился к учебнику 8086 to Pentium Assembly Programming, но не смог найти решение этой проблемы.

Мой код:

#include<stdio.h>

void main()
{
    float array[10] = { 1.13,1.98,1.67,1.19},sum;
asm{
    lea ax,[array]
    push ax
    fld dword ptr[bp-8]
    fstp sum
}
printf("%f", sum);
}

Он компилируется без ошибок, но при запуске я получаю -786,997 вместо ожидаемого результата 1,67.


person Madhu    schedule 05.08.2019    source источник
comment
Выполнение push ax того, что вы никогда не pop, кажется верным путем к неприятностям.   -  person Nate Eldredge    schedule 05.08.2019
comment
Также вы не можете полагаться на адрес массива bp-8.   -  person Jester    schedule 05.08.2019
comment
Для какого компилятора вы программируете?   -  person fuz    schedule 05.08.2019
comment
@fuz: согласно вопросу, они используют турбо-С++ 3.0.   -  person Michael Petch    schedule 05.08.2019
comment
Вы решили свою проблему?   -  person Michael Petch    schedule 13.08.2019


Ответы (1)


В этом коде:

    lea ax,[array]
    push ax
    fld dword ptr[bp-8]
    fstp sum

Вы загружаете адрес array в AX, а не значение. Затем вы помещаете адрес array в AX в стек. Затем ваша инструкция FLD пытается считать данные с фиксированного смещения относительно BP. Как указывает @Jester, вы не должны полагаться на то, что данные в стеке представляют собой конкретное смещение от BP, поскольку это зависит от генератора кода Turbo-C и от того, где элементы размещены в стеке.

Если вы хотите прочитать третий элемент массива, вы можете загрузить адрес массива, а затем получить доступ к адресам отдельных элементов. Загрузите адрес array в регистры BX, SI или DI, поскольку их можно использовать в качестве базы в 16-битный режим адресации (AX не может).

Ваш код мог бы выглядеть так:

#include<stdio.h>

void main()
{
    float array[] = { 1.13,1.98,1.67,1.19 }, sum;
    asm{
        lea bx,[array]      /* Load address of array into BX */
        fld dword ptr[bx+8] /* Load the value at 3rd element. Each float is 4 bytes
                               in 16-bit Turbo-C thus [bx+8] is the third element */
        fstp [sum]          /* Store top of stack ST(0) to SUM and pop top of stack */
    }
    printf("%f", sum);
}

Код, который будет суммировать массив с плавающей запятой от самого высокого до самого низкого элемента массива, может выглядеть так:

#include<stdio.h>

void main()
{
    float array[] = { 1.13,1.98,1.67,1.19 }, sum;
    const int array_size_b = sizeof (array);
                            /* Size of array in bytes */
    asm {
        lea bx,[array]      /* Load address of array into BX */
        mov si, [array_size_b]
                            /* SI = byte offset to element just past end of array */
        fldz                /* Push an initial SUM value (0.0) on the FPU stack */
    }
    sumloop:
    asm {
        fadd dword ptr[bx+si-4]
                            /* Add current float to SUM on top of FPU stack */
        sub si, 4           /* Set index to previous float in array */
        jnz sumloop         /* If not start of array go back and process next element */

        fstp [sum]          /* Retrieve SUM from top of FPU stack&store in variable sum */
    }
    printf("%f", sum);
}

Обработка элементов в обратном порядке упрощает логику проверки того, обработали ли мы весь массив. Это можно было бы сделать от первого элемента до последнего:

#include<stdio.h>

void main()
{
    float array[] = { 1.13,1.98,1.67,1.19 }, sum;
    const int array_size_b = sizeof (array);
    asm {
        lea bx,[array]       /* Load address of array into BX */
        xor si, si           /* SI = index into array = 0 = first element */
        mov cx, [array_size_b]
                             /* CX = byte offset of element just past end of array */
        fldz                 /* Push an initial SUM value (0.0) on the FPU stack */
    }
    sumloop:
    asm {
        fadd dword ptr[bx+si]/* Add the current float to SUM on top of FPU stack */
        add si, 4            /* Advance index to next float in array */
        cmp si, cx           /* Has the index reached the end of array? */
        jl sumloop           /* If not end of array go back and process next element */

        fstp [sum]           /* Retrieve SUM from top of FPU stack&store in variable sum */
    }
    printf("%f", sum);
}

Наблюдения

Фактически существует два типа стеков на процессорах с x87 FPU (блок с плавающей запятой). Стек вызовов, на который указывает SS:SP, и стек регистров x87 FPU. Если вы помещаете что-то в стек вызовов, инструкции стека FPU, которые извлекают верхний элемент, извлекаются только из стека регистров FPU. Если вы помещаете что-то в стек вызовов с помощью push ax, вы нарушаете балансировку стека вызовов, и вам следует подумать о его повторной балансировке, когда ваша встроенная сборка будет завершена. Вы можете использовать pop ax для этого или add sp, 2.

person Michael Petch    schedule 05.08.2019
comment
Более явным термином для стека SS:SP является стек вызовов, а не регистры стека x87. - person Peter Cordes; 06.08.2019