从printf说开去(五)

2024-01-03 01:19
文章标签 开去 printf

本文主要是介绍从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字节的数据呢,为什么不会为一个不确定的值?

 

(未完待续)

这篇关于从printf说开去(五)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/564178

相关文章

printf sprintf fprintf

c语法 1.printf  .   sprintf   .   fprintf 1.1.printf 是把格式字符串输出到标准输出(一般是屏幕,可以重定向)。  1.2.sprintf sprintf,是把格式字符串输出到指定字符串中,所以参数比printf多一个char*。那就是目标字符串地址。 1.3fprintf fprintf, 是把格式字符串输出到指定文件设备中,所

printf打印带颜色的字体和背景

格式如下: printf("\033[字背景颜色;字体颜色m 字符串 \033[0m" ); 例子: printf("\033[1m\033[45;33m HELLO_WORLD \033[0m\n"); 颜色代码: QUOTE:  字背景颜色范围: 40--49                   字颜色: 30—39               40: 黑

printf笔记

1、控制打印颜色 1.1、通用格式: Esc[{attr1};{attr2};...;{attrn}m Esc为转义字符,值为:\033 {attr1}{attr2}{attrn}为属性,属性之间用分号相隔 红色前景输出: \033[31m 1.2、属性列表: 1.2.1、通用格式 0 重置所有属性 1 高亮/加粗 2 暗淡 4 下划线 5 闪烁 7 反转 8 隐藏

Printf 的格式输出探索

#include<stdio.h>int main() //%[flags][width][.prec][hlL]type{ printf("%9d\n",123);//数字输出占据9个字符空间,右对齐printf("%-9d\n",123);//左对齐printf("%+9d\n",123);//^^^^^+123printf("%+-9d\n",123);//+123printf("%-+

使用C标准库中的printf输出

1、增加文件系统调用 对系统调用进行了调整,一是将所有的系统调用实现转移 从头文件转移到C文件中; 二是增加几个有关文件打开和关闭的接口 主要是将系统调用做成单独的app库,这个库可以供其它所有的应用程序使用 2、导入newlib库,并调用 newlib可移植性强,具有可重入特性、功能完备等特点。Newlib的所有库函数都建立在20个桩函数的基础上[2],这20个桩函数完成一些ne

c printf 缓冲区分析

printf行缓冲区的分析总结 2013-08-18 12:29  5222人阅读  评论(7)  收藏  举报   分类: app program(9)  版权声明:本文为博主kerneler辛苦原创,未经允许不得转载。   最近在客户那调试串口的时候,read串口然后printf打印,单字符printf,发现没有输出,后来想起来printf这些

【STM32开发笔记】STM32H7S78-DK上的CoreMark移植和优化--兼记STM32上的printf重定向实现及常见问题解决

【STM32开发笔记】STM32H7S78-DK上的CoreMark移植和优化--兼记STM32上的printf重定向实现及常见问题解决 一、CoreMark简介二、创建CubeMX项目2.1 选择MCU2.2 配置CPU时钟2.3 配置串口功能2.4 配置LED引脚2.5 生成CMake项目 三、基础功能支持3.1 支持记录耗时3.2 支持printf输出到串口3.3 支持printf输出

IAR CC2530调试输出printf打印信息

1、很多网友给出的操作是:在General Options-> LibratyConfiguration下选择Smihoste 但是,我用的IAR8.10.3 (8.10.3.40338),根本无此选项,如下图: 2、采用另一种方式,在调试的过程中view->Terminal IO, 即可看到printf打印。

【STM32 HAL】多串口printf重定向

【STM32 HAL】多串口printf重定向 前言单串口printf重定向原理实现CubeMX配置Keil5配置 多串口printf重定向 前言 在近期项目中,作者需要 STM32 同时向上位机和手机发送数据,传统的 printf 重定向只能输出到一个串口。本文介绍如何实现 printf 同时输出到多个串口 单串口printf重定向 原理 为了使用 printf() 函

stm32的UART重定向printf()

1配置好uart 2打开usart.c文件 3在此文件前面添加头文件 4在末尾添加重定向代码 添加的代码 /* USER CODE BEGIN 1 *///加入以下代码,支持printf函数,而不需要选择use MicroLIB //#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f) #if 1//#pragma i