【问题标题】:Mysterious side-effect of calling printf or not in C before calling an asm function?在调用 asm 函数之前是否在 C 中调用 printf 的神秘副作用?
【发布时间】:2018-05-30 10:53:41
【问题描述】:

该程序必须按照用户提供的精度计算 pi。 calculate_pi() 函数是用 NASM 编写的。 有人可以向我解释为什么如果这一行被评论:

//printf("accuracy: %.15f\n", precision);       //<- This line

程序无法正常运行。向 calcuta_pi() 函数发送奇怪的数字?如果注释了这一行,则会向函数发送一个非常小的值,并且程序会无限运行。

但如果不是注释程序也可以正常工作。

#include <stdio.h>
#include <math.h>

extern double calculate_pi(double precision); /* external function declaration */

double calculate_pi(double precision);   /* function prototype */

int main()
{
double precision = 1;

printf("A program that calculates pi, with accuracy provided by the user\n");
printf("Give me accuracy\n");

while(1)
{
    if (scanf("%lf", &precision) != 1)
    {
        printf("reading error\n");

        fseek(stdin,0,SEEK_END);

        continue;
    }
    if(precision<0)
        precision = fabs(precision);

    //printf("accuracy: %.15f\n", precision);       //<- This line
    printf("pi: %.15f\n", calculate_pi(precision));
}

return 0;
}

这是我的汇编代码:

;   arctg(1)=a
;   tg(arctg(1))=tg(a)
;   atan(x) = x - x^3/3 + x^5/5 - x^7/7 + x^9/9..
;   PI/4 = atan(1) = 1 - 1/3 + 1/5 - 1/7 + 1/9...
;   PI = (4/1) - (4/3) + (4/5) - (4/7) + (4/9) - (4/11) + (4/13) - (4/15) ...


section .text use32

global  _calculate_pi

_calculate_pi:

%idefine    a   [ebp+12]


;ramka stosu
push    ebp
mov ebp, esp

;ustawianie zmiennych
fld qword [const_wynik]
fstp qword [wynik]

fld qword [const_licznik]
fstp qword [licznik]

fld qword [const_mianownik]
fstp qword [mianownik]

.loop:
finit ; inicjalizacja stosu FPU

fld qword [licznik]         ;licznik na stos
fld qword [mianownik]       ;mianownik na stos
fdiv                        ;wynik dzielenia st1/st0
fadd qword [wynik]          ;st0 = wynik dzielenia + [wynik]

fstp qword [wynik]          ;wywalamy z st0 do [wynik]

                            ;zmieniamy mianownik + 2
fld qword [mianownik]       ;mianownik na stos
fadd qword [zwiekszmian]    ;st0 = mianownik + 2
fstp qword [mianownik]          ;wywalamy z st0 do [mianownik]

                            ;zmieniamy licznik *(-1)
fld qword [licznik]         ;licznik na stos
fchs                        ;st0 = -st0 = -licznik
fstp qword [licznik]        ;wywalamy z st0 do [licznik]

                        ;sprawdzanie dokladnosci
fld qword[wynik]        ;wynik na stos
fldpi                   ;pi na stos
fsub                    ;st0 = wynik-pi = st1 - st0
fabs                    ;st0 = |wynik-pi|

fld qword a             ;st0 = zadana dokladnosc

                            ;(Unordered Compare ST(0) to ST(i) and set CPU flags and Pop ST(0))
                            ;Przyrostek p oznacza obniżenie stosu rejestrów koprocesora, przyrostek i oznacza zapisywanie wyników bezpośrednio do flag procesora a nie flag koprocesora
fucomip st0, st1            ;porownanie z dokladnoscia  if(zadana dokladnosc > uzyskana)


jb    .loop   ;only the C0 bit (CF flag) would be set if no error


fld qword [wynik]

        ;zwraca to co w st0
leave   ; LEAVE = mov esp, ebp / pop ebp
ret


section .data:

wynik       dq 4.0
licznik     dq -4.0
mianownik   dq 3.0
zwiekszmian dq 2.0

const_wynik     dq 4.0
const_licznik   dq -4.0
const_mianownik dq 3.0

样本输出:

我正在使用:

  • NASM 版本 2.11.06 编译于 2014 年 10 月 20 日
  • gcc (MinGW.org GCC-6.3.0-1) 6.3.0

编译和汇编命令:

nasm -o pi.o -f coff pi.asm
gcc pi.o pi_interface.c -o projekt.exe -Wall -Wextra

【问题讨论】:

  • 这不是一种非常用户友好的方式来指定准确性。正确数字的数量会更多。
  • 请解释它在哪些方面不能正常工作。
  • 似乎汇编函数没有遵循目标平台的正确调用约定,并且在汇编调用之前使用printf 代码,您会很幸运,并且偶然选择了正确的值。尝试在调试器中检查两者的入口状态有何不同。你甚至没有显示calculate_pi 函数的声明,也没有显示它的 asm 源,你认为我们在这里是神谕吗? :D(因为甚至没有接近minimal reproducible example,所以在某个时刻,当你在浪费每个人的时间时)
  • @Ped7g:正确的回答是基于这个原因的近距离投票。因缺少 MCVE 而投反对票是一种选择,但在没有实际投票的情况下这样做是没有意义的(除非您将密切投票保存为副本)。
  • Leo: edit 您的问题与相关来源(您可以创建将返回参数精度的占位符程序集),构建它,确保它显示相同的问题,然后将完整的可构建来源问题+您的目标平台+您如何构建可执行文件,因此任何可以访问类似平台的人都可以尝试重现您的问题。 @PeterCordes 我将“关闭”投票保存到 OP 不沟通并且没有改善 Q 的时刻。一旦 Q 关闭,在我看来,很难恢复活力。

标签: windows gcc assembly x86 nasm


【解决方案1】:

我认为您错误地访问了函数 arg,偏移了 4 个字节。当您创建堆栈帧时,第一个参数位于 [ebp+8],但您从 [ebp+12] 加载。 (这适用于在堆栈上传递 args 的所有调用约定。我认为 32 位 mingw 默认为 double 这样做。)

这意味着您作为precision 使用的double 值的高4 字节来自调用者碰巧留在堆栈上8 字节arg 槽上方的任何内容。这就解释了为什么调用者的变化会影响你的函数的行为,以及为什么你会得到一个无限循环:如果你加载的字节恰好代表一个非常小的double,你的循环永远不会退出。

低 4 个字节(尾数的 32 个最低有效位)来自调用者传递的前 4 个字节。

通过查看寄存器并注意到您加载的值不是调用者传递的值,您可以使用调试器轻松找到这一点。此外,@Ped7g 建议尝试在一个简单的 asm 函数中返回 precision 也会发现问题。

【讨论】:

    猜你喜欢
    • 2012-12-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-23
    • 1970-01-01
    • 2016-09-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多