从printf说开去(五)

   (接上文)

 

   对于printf(“%f”, 10/3, 0×40080000);能输出3.000000的结果,根据前面几篇文章的内容稍作分析不难得出:
   能输出这个结果,其实和printf的第二个参数10/3一点关系也没有,改为:printf(“%f”, 0, 0×40080000);也能输出这个结果。
   这里利用不定参数表构造出了一个值:0×4008000000000000(如果第二个参数为3,那么就是0×4008000000000003),这个值就是双精度的3.0在内存中的存储形式。
   当printf解析第一个参数格式化字符串后,以double方式来处理传入的参数,也就是请求一个8字节的数据,当前堆栈状态如下:

 

                                                        栈顶
                                            ————————————-
                                                  格式化字符串地址
可变参数起始地址–>   ————————————-
                                               不定参数第一个参数值0×00000000
                                            ————————————-
                                               不定参数第二个参数值0×40080000

 

    看起来我们多传递了一个参数,实际上这个参数被正确的用到了。在请求8字节的数据后,得到的值为:0×4008000000000000,显示出来就是3.000000

    可以用上一篇文章写的sumfunf函数来验证一下:sumfunf(1, 0×00000000, 0×40080000);
    程序按32bit方式编译的情况下,printf(“%f”, 0, 0×40080000);与printf(“%f”, 0×4008000000000000);等价。

    大家知道,C/C++中的参数传递主要是通过堆栈进行,对于printf这样的函数来说,可变参数部分多一个、少一个参数编译器都不会给出任何提示,甚至只存在格式化字符串一个参数(如:printf(“%f”);),也能编译通过执行。


    以调用sumfunf(1, 0×00000000, 0×40080000) 为例,编译后,代码被翻译为类似这样(示意):

        push        40080000h
        push        0   
        push        1   
        call        _sumfunf
        add         esp,0Ch

    稍微解释一下:push是将一个值压入堆栈,值在栈顶处压入,压入后栈顶上移一个字长,esp保存的是当前栈顶地址。

        栈顶(esp指向)
    ———————————–
        …已存在于堆栈的数据

    执行push        40080000h 压栈后:

        栈顶(esp指向)
    ———————————–
         0×40080000
    ———————————–
        …已存在于堆栈的数据

    执行push 0压栈后:

        栈顶(esp指向)
    ———————————–
         0×00000000
    ———————————–
         0×40080000
    ———————————–
        …已存在于堆栈的数据

    执行push 1压栈后:

        栈顶(esp指向)
    ———————————–
         0×00000001
    ———————————–
         0×00000000
    ———————————–
         0×40080000
    ———————————–
        …已存在于堆栈的数据

 

    在函数里面取参数的时候,也使用这个约定,按顺序从调用时的栈顶开始取值,第一个参数,第二个参数,第三个参数…
    当函数调用完成后,需要进行退栈,于是将esp加上0ch(十进制12),压入了3个数据,每个数据占用4字节,总占用12字节,所以退栈的时候需要加12字节。


    退栈后,栈顶又恢复了初始的值:

                                                                    0×00000001
                                                     ———————————–
                                                                    0×00000000
                                                     ———————————–
                                                                    0×40080000
退栈后栈顶(esp指向)—>    ———————————–
                                                            …已存在于堆栈的数据
    试试用hack一点的手法来调用函数sumfunf(1, 0, 0×40080000),等价于:
    _asm {
        push        40080000h
        push        0   
    }

    float r = sumfunf(1);  // 为了绕过语法检查,给出第一个参数

    _asm add         esp,08h

 

         实际上,程序被编译为32bit的机器码执行,即是是写成sumfunf(1, 0×4008000000000000)这样的形式,编译器也处理为在入栈时拆分为2个字长进行处理。push默认是使用机器字长进行处理,如果机器字长是32bit,则是4个字节,每压栈一个数据栈顶指针减4;如果是16bit机器字长,栈顶指针减2,同理,如果是64bit,栈顶指针则减去8。所以可以知道如果按64bit字长编译的情况下,sumfunf(1, 0×00000000, 0×40080000)就不能输出原本期望的3.000000了。

 

    讨论参数堆栈传递机制的背景知识,是为了引出这样一个问题:
    按照这种说法,在32bit机器字长的情况下,为什么printf(“%f”, 3.0f);能输出期望的3.000000?sizeof(3.0f) 也等于4字节,如果说%f代表着期望获得一个8字节的double类型数据,那么还有4字节的数据呢,为什么不会为一个不确定的值?

 

(未完待续)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值