从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字节的数据呢,为什么不会为一个不确定的值?

 

(未完待续)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值