STM32 芯片启动过程

目录

  • 一、前言
  • 二、STM32 的启动模式
  • 三、STM32 启动文件分析
    • 1、栈 Stack
    • 2、堆 Heap
    • 3、中断向量表 Vectors
      • 3.1 中断响应流程
    • 4、复位程序 Reset_Handler
    • 5、中断服务函数
    • 6、用户堆栈初始化
  • 四、STM32 启动流程分析
    • 1、初始化 SP、PC 及中断向量表
    • 2、设置系统时钟
    • 3、初始化堆栈并进入 main
  • 五、总结


一、前言

下面主要讲解从上电复位到 main 函数的过程。主要有以下步骤:

  1. 初始化堆栈指针:SP = __initial_spPC = Reset_Handler
  2. 初始化中断向量表
  3. 配置系统时钟
  4. 调用 C 库函数 _main 初始化用户堆栈,然后进入 main 函数

二、STM32 的启动模式

启动模式决定了中断向量表的位置,STM32 有三种启动模式:

  1. 主闪存存储器启动:从 STM32 内置的 Flash 启动( 0 x 08000000 − 0 x 0807 F F F F 0x0800 0000-0x0807 FFFF 0x080000000x0807FFFF),一般我们使用 JTAG 或者 SWD 模式下载程序时,就是下载到这个里面,重启后也直接从这启动程序。以 0x08000000 对应的内存为例,则该块内存既可以通过 0x00000000 操作也可以通过 0x08000000 操作,且都是操作的同一块内存。
  2. 系统存储器启动:从系统存储器启动( 0 x 1 F F F F 000 − 0 x 1 F F F F 7 F F 0x1FFFF000-0x1FFF F7FF 0x1FFFF0000x1FFFF7FF),这种模式启动的程序功能是由厂家设置的。一般来说,我们选用这种启动模式时,是为了从串口下载程序,因为在厂家提供的 ISP 程序中,提供了串口下载程序的固件,可以通过这个 ISP 程序将用户程序下载到系统的 Flash 中。以 0x1FFFFFF0 对应的内存为例,则该块内存既可以通过 0x00000000 操作也可以通过 0x1FFFFFF0 操作,且都是操作的同一块内存。
  3. 片上 SRAM 启动:从内置 SRAM 启动( 0 x 20000000 − 0 x 3 F F F F F F F 0x2000 0000-0x3FFFFFFF 0x200000000x3FFFFFFF),既然是 SRAM,自然也就没有程序存储的能力了,这个模式一般用于程序调试。SRAM 只能通过 0x20000000 进行操作,与上述两者不同。从 SRAM 启动时,需要在应用程序初始化代码中重新设置向量表的位置。

我们可以选择设置 BOOT0 和 BOOT1 的引脚电平状态,来选择复位后的启动模式,如下表:

BOOT1BOOT0自举模式自举空间
x0主 Flash选择主 Flash 作为自举空间
01系统存储器选择系统存储器作为自举空间
11嵌入式 SRAM选择嵌入式 SRAM 作为自举空间

启动模式只决定程序烧录的位置,加载完程序之后会有一个重映射(映射到 0x00000000 地址位置);真正产生复位信号的时候,CPU 还是从开始位置执行

值得注意的是 STM32 上电复位以后,代码区都是从 0x00000000 开始的,三种启动模式只是将各自存储空间的地址映射到 0x00000000 中。

三、STM32 启动文件分析

下面针对 startup_stm32f40_41xxx.s 文件进行简单的分析。

1、栈 Stack

Stack_Size      EQU     0x00000400AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size
__initial_sp

这段代码的含义如下:开辟栈的大小为 0X00000400(1KB),名字为 STACK, NOINIT 即不初始化,可读可写, ALIGN=3 表示 8(2^3)字节对齐。

栈的作用是用于局部变量,函数调用,函数形参等的开销,栈的大小不能超过内部 SRAM 的大小。如果编写的程序比较大,定义的局部变量很多,那么就需要修改栈的大小。

  • EQU:宏定义的伪指令,相当于等于,类似于 C 中的 define。
  • AREA:告诉汇编器汇编一个新的代码段或者数据段。STACK 表示段名,这个可以任意命名;NOINIT 表示不初始化;READWRITE 表示可读可写,ALIGN=3,表示按照 2 3 2^3 23 对齐,即 8 字节对齐。
  • SPACE:用于分配一定大小的内存空间,单位为字节。这里指定大小等于 Stack_Size。
  • 标号 __initial_sp 紧挨着 SPACE 语句放置,表示栈的结束地址,即栈顶地址,栈是由高向低生长的。

2、堆 Heap

Heap_Size       EQU     0x00000200AREA    HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem        SPACE   Heap_Size
__heap_limitPRESERVE8THUMB

解释如下:开辟堆的大小为 0X00000200(512 字节),名字为 HEAP, NOINIT 即不初始化,可读可写,ALIGN=3 同理表示 8(2^3)字节对齐。__heap_base 表示堆的起始地址,__heap_limit 表示堆的结束地址。堆是由低向高生长的,跟栈的生长方向相反。

  • PRESERVE8:指定当前文件的堆栈按照 8 字节对齐。
  • THUMB:表示后面指令兼容 THUMB 指令。THUBMARM 指令集的一个子集重新编码二形成的一个指令集,其指令长度为 16bit,现在 Cortex-M 系列的都使用 THUMB-2 指令集,THUMB-2 是 32 位的,兼容 16 位和 32 位的指令,是 THUMB 的超集。

堆主要用来动态内存的分配,像 malloc() 函数申请的内存就在堆上面。不过这个在 STM32 里面用的比较少。

3、中断向量表 Vectors

; Vector Table Mapped to Address 0 at ResetAREA    RESET, DATA, READONLYEXPORT  __VectorsEXPORT  __Vectors_EndEXPORT  __Vectors_Size

这里定义一个数据段,名字为 RESET,可读。并声明 __Vectors__Vectors_End__Vectors_Size 这三个标号具有全局属性,可供外部的文件调用。

  • EXPORT:声明一个标号可被外部的文件使用,使标号具有全局属性。如果是 IAR 编译器,则使用的是 GLOBAL 这个指令。

当内核响应了一个发生的异常后,对应的异常服务例程(ESR)就会执行。为了决定 ESR 的入口地址, 内核使用了向量表查表机制。这里使用一张向量表。向量表其实是一个 WORD(32 位整数)数组,每个下标对应一种异常,该下标元素的值则是该 ESR 的入口地址。向量表在地址空间中的位置是可以设置的,通过 NVIC 中的一个重定位寄存器来指出向量表的地址。在复位后,该寄存器的值为 0。因此,在地址 0 (即 FLASH 地址 0) 处必须包含一张向量表,用于初始时的异常分配。要注意的是这里有个另类:0 号类型并不是什么入口地址,而是给出了复位后 MSP 的初值。下图是 F407 的向量表的一部分,具体参阅想要使用的芯片对应的手册即可。

可以看到和代码中的定义是一一对应的:

__Vectors       DCD     __initial_sp               ; Top of StackDCD     Reset_Handler              ; Reset HandlerDCD     NMI_Handler                ; NMI HandlerDCD     HardFault_Handler          ; Hard Fault HandlerDCD     MemManage_Handler          ; MPU Fault HandlerDCD     BusFault_Handler           ; Bus Fault HandlerDCD     UsageFault_Handler         ; Usage Fault HandlerDCD     0                          ; ReservedDCD     0                          ; ReservedDCD     0                          ; ReservedDCD     0                          ; ReservedDCD     SVC_Handler                ; SVCall HandlerDCD     DebugMon_Handler           ; Debug Monitor HandlerDCD     0                          ; ReservedDCD     PendSV_Handler             ; PendSV HandlerDCD     SysTick_Handler            ; SysTick Handler......__Vectors_End__Vectors_Size  EQU  __Vectors_End - __Vectors

向量表从 FLASH 的 0 地址开始放置,以 4 个字节为一个单位,地址 0 存放的是栈顶地址, 0X04 存放的是复位程序的地址,以此类推。从代码上看,向量表中存放的都是中断服务函数的函数名,可我们知道 C 语言中的函数名就是一个地址。

  • DCD:分配一个或者多个以字为单位的内存,以四字节对齐,并要求初始化这些内存。在向量表中,DCD 分配了一堆内存,并且以 ESR 的入口地址初始化它们。

3.1 中断响应流程

这里既然提到了中断向量表,就额外补充以下中断响应流程:

  1. 中断信号发送到 NVIC
  2. NVIC 通知 CPU
  3. CPU 根据中断号得到中断服务程序地址(基地址 + 中断编号 * 4B)
  4. 保存现场
  5. 执行中断服务程序
  6. 恢复现场
  7. 继续执行程序

下面以 PendSV_Handler 为例,说明一下:

可以看到 PendSV_Handler 的中断号是 14,也就是 14 ∗ 4 = ( 56 ) 10 = ( 38 ) 2 14*4=(56)_{10}=(38)_2 144=(56)10=(38)2

而中断向量表的基地址为 0x08000000,故其服务函数的地址在 0x08000038,见下 bin 文件(下面会提到):

解析出来是:0x080002D7,而 map 文件中却是:0x080002D6

原因如下:ARM 指令集有 ARM 指令集和 Thumb 指令集。ARM 指令集位数长;而 Thumb 指令集位数短,故而占用内存比较小,所以编译器大部分时间采用 Thumb 指令集。

  • Thumb 指令集末尾是奇数位 1,像这里的 0x080002D7
  • ARM 指令集末尾是偶数位 0,也就是这里的 0x080002D6

而我们访问 0x080002D7 这个地址的时候,它实际上会跳转到 0x080002D6 这个地址。

4、复位程序 Reset_Handler

				AREA    |.text|, CODE, READONLY; Reset handler
Reset_Handler    PROCEXPORT  Reset_Handler             [WEAK]IMPORT  SystemInitIMPORT  __mainLDR     R0, =SystemInitBLX     R0LDR     R0, =__mainBX      R0ENDP

首先定义一个名称为 .text 的代码段,仅可读。

复位子程序是系统上电后第一个执行的程序,调用 SystemInit 函数初始化系统时钟,然后调用 C 库函数 __main,最终调用 main 函数去到 C 程序中。

  • WEAK:表示弱定义,如果外部文件优先定义了该标号则首先引用该标号,如果外部文件没有声明也不会出错。这里表示复位子程序可以由用户在其他文件重新实现,这里并不是唯一的。
  • IMPORT:表示该标号来自外部文件,跟 C 语言中的 EXTERN 关键字类似。这里表示 SystemInit__main 这两个函数均来自外部的文件。
  • LDR:从存储器中加载字到一个寄存器中
  • BL:跳转到由寄存器/标号给出的地址,并把跳转前的下一条指针地址保存到 LR
  • BLX:跳转到由寄存器给出的地址,并根据寄存器的 LSE 确定处理器的状态,还要把跳转前的下条指令地址保存到 LR
  • BX:跳转到由寄存器/标号给出的地址,不用返回

SystemInit() 是一个标准的库函数,在 system_stm32f4xx.c 这个库文件中定义。主要作用是配置系统时钟。__main 是一个标准的 C 库函数,主要作用是初始化用户堆栈,并在函数的最后调用 main 函数去到 C 程序中。这就是为什么我们写的程序都有一个 main 函数的原因。

5、中断服务函数

在启动文件里面已经帮我们写好所有中断的中断服务函数,跟我们平时写的中断服务函数不一样的就是这些函数都是空的,真正的中断服务程序需要我们在外部的 C 文件里面重新实现,这里只是提前占了一个位置而已。

如果我们在使用某个外设的时候,开启了某个中断,但是又忘记编写配套的中断服务程序或者函数名写错,那当中断来临的时,程序就会跳转到启动文件预先写好的空的中断服务程序中,并且在这个空函数中无线循环,即程序就死在这里。

NMI_Handler     PROCEXPORT  NMI_Handler                [WEAK]B       .ENDP...
SysTick_Handler PROCEXPORT  SysTick_Handler            [WEAK]B       .ENDPDefault_Handler PROCEXPORT  WWDG_IRQHandler                   [WEAK]    ...
CRYP_IRQHandler                                                    
HASH_RNG_IRQHandler
FPU_IRQHandlerB       .ENDP
  • B:跳转到一个标号。这里跳转到一个‘.’,即表示无线循环

6、用户堆栈初始化

ALIGN;*******************************************************************************
; User Stack and Heap initialization, 由 C 库函数 __main 来完成
;*******************************************************************************IF      :DEF:__MICROLIB  ; 这个宏在 KEIL 里面开启EXPORT  __initial_spEXPORT  __heap_baseEXPORT  __heap_limitELSEIMPORT  __use_two_region_memory   ; 这个函数由用户自己实现EXPORT  __user_initial_stackheap__user_initial_stackheapLDR     R0, =  Heap_MemLDR     R1, =(Stack_Mem + Stack_Size)LDR     R2, = (Heap_Mem +  Heap_Size)LDR     R3, = Stack_MemBX      LRALIGNENDIFEND
  • ALIGN:对指令或者数据存放的地址进行对齐,后面会跟一个立即数。缺省表示 4 字节对齐。
  • IF,ELSE,ENDIF:汇编的条件分支语句,跟 C 语言的 if ,else 类似
  • END:文件结束

首先判断是否定义了 __MICROLIB

  • 如果定义了这个宏则赋予标号 __initial_sp(栈顶地址)、__heap_base(堆起始地址)、__heap_limit(堆结束地址)全局属性,可供外部文件调用。有关这个宏我们在 KEIL 里面配置,具体见下图。然后堆栈的初始化就由 C 库函数 __main 来完成。
  • 如果没有定义 __MICROLIB,则才用双段存储器模式,且声明标号 __user_initial_stackheap 具有全局属性,让用户自己来初始化堆栈。

四、STM32 启动流程分析

经过刚才对启动文件的分析,下面对 STM32 启动流程的分析就要清晰不少了。

1、初始化 SP、PC 及中断向量表

当系统复位后,处理器首先读取向量表中的前两个字(8 个字节),第一个字存入 SP,第二个字存入 PC,也就是
程序执行的起始地址。

下面打开经过编译生成的 bin 文件,看到前 8 个字节的内容如下(小端模式):

Keil 默认生成 hex 文件,要想生成 bin 文件要自己添加命令,设置如下:

D:\Keil_v5\ARM\ARMCC\bin\fromelf.exe --bin --output=Objects/stm32f407.bin Objects/stm32f407.axf

0x200006600x0800020D,下面在 map 文件(存放链接地址)中查找这两个地址存放的是什么数据:

在这里插入图片描述

这正是中断向量表的前两项内容,这也印证了前面所说的内容:

2、设置系统时钟

接下来执行 PC 指向的 Reset_Handler,并调用 SystemInit 初始化系统时钟。

void SystemInit(void)
{/* FPU settings ------------------------------------------------------------*/#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));  /* set CP10 and CP11 Full Access */#endif/* Reset the RCC clock configuration to the default reset state ------------*//* Set HSION bit */RCC->CR |= (uint32_t)0x00000001;/* Reset CFGR register */RCC->CFGR = 0x00000000;/* Reset HSEON, CSSON and PLLON bits */RCC->CR &= (uint32_t)0xFEF6FFFF;/* Reset PLLCFGR register */RCC->PLLCFGR = 0x24003010;/* Reset HSEBYP bit */RCC->CR &= (uint32_t)0xFFFBFFFF;/* Disable all interrupts */RCC->CIR = 0x00000000;#if defined(DATA_IN_ExtSRAM) || defined(DATA_IN_ExtSDRAM)SystemInit_ExtMemCtl(); 
#endif /* DATA_IN_ExtSRAM || DATA_IN_ExtSDRAM *//* Configure the System clock source, PLL Multiplier and Divider factors, AHB/APBx prescalers and Flash settings ----------------------------------*/SetSysClock();/* Configure the Vector Table location add offset address ------------------*/
#ifdef VECT_TAB_SRAMSCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
#elseSCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
#endif
}

前面的部分是配置时钟的,具体参考手册即可。需要注意的是最后一段代码:

默认是没有开启 VECT_TAB_SRAM,则从 FLASH 中启动,VTOR 寄存器存放的是中断向量表的起始地址,在 IAP 升级会修改这里的偏移量。

3、初始化堆栈并进入 main

执行完 SystemInit 后又调用了标准库中的 __main 函数:

在这里会初始化堆、栈、RO、RW、ZI 段。最后就进入到 C 文件中的 main 函数中。

有关内存分段的内存可以参考:单片机内存区域划分

五、总结

至此,启动过程圆满结束!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/426746.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Transformer深入学习】之一:Sinusoidal位置编码的精妙

看苏神的文章提到&#xff1a;Transformer原论文使用Sinusoidal位置编码&#xff0c;作为位置编码的一个显式解&#xff0c;Google 在原论文中对它的描述寥寥无几&#xff0c;只是简单提及了它可以表达相对位置信息&#xff0c;并未提及这个编码的合理性。 看了几篇文章&#x…

云计算实训50——Kubernetes基础命令、常用指令

一、Kubernetes 自动补齐 # 安装自动补齐软件 [rootmaster ~]# yum -y install bash-completion # 临时开启自动补齐功能 [rootmaster ~]# source # 永 久开启自动补齐功能 [rootmaster ~]# echo "source > ~/.bashrc 二、Kubernetes 基础命令 kubectl [command] …

【数据结构】数据结构系列学习笔记——导航篇

一&#xff1a;概述 数据结构是计算机科学中的核心概念之一&#xff0c;是优化算法性能和资源利用率的关键。在软件开发和数据处理中&#xff0c;选择合适的数据结构对于算法的效率至关重要。数据结构的选择通常基于数据的使用模式&#xff0c;包括数据元素之间的关系、数据的存…

【专题】2024中国生物医药出海现状与趋势蓝皮书报告合集PDF分享(附原数据表)

原文链接&#xff1a;https://tecdat.cn/?p37719 出海已成为中国医药产业实现提速扩容的重要途径。目前&#xff0c;中国医药产业发展态势良好&#xff0c;创新能力不断增强&#xff0c;然而也面临着医保政策改革和带量集采带来的压力。政府积极出台多项政策支持医药企业出海…

Vim编辑器常用命令

目录 一、命令模式快捷键 二、编辑/输入模式快捷键 三、编辑模式切换到命令模式 四、搜索命令 一、命令模式快捷键 二、编辑/输入模式快捷键 三、编辑模式切换到命令模式 四、搜索命令

我的AI工具箱Tauri版-VideoReapeat视频解说复述克隆

本教程基于自研的AI工具箱Tauri版进行VideoReapeat视频解说复述克隆。 视频解说复述克隆样片 《我的AI工具箱Tauri版-VideoReapeat视频解说复述克隆》样片 进入软件后可以直接搜索 VideoReapeat 或者依次点击 Python音频技术/视频tools 进入该模块。 该模块会消耗TTS文本转语…

【四范式】浅谈NLP发展的四个范式

自然语言处理&#xff08;Natural Language Processing&#xff0c;NLP&#xff09;是计算机科学&#xff0c;人工智能&#xff0c;语言学关于计算机和人类自然语言之间的相互作用的领域&#xff0c;是计算机科学领域与人工智能领域中的一个重要方向。NLP发展到今天已经进入到了…

kubernetes架构

kubernetes cluster由master和node组成&#xff0c;节点上运行着若干kubernetes服务Master节点&#xff1a; master是kubernetes cluster的大脑&#xff0c;运行着的Daemon服务包括kube-apiserver&#xff0c;kube-scheduler,kube-controller-manager&#xff0c;etcd和Pod网络…

Dify 中的讯飞星火平台工具源码分析

本文主要对 Dify 中的讯飞星火平台工具 spark 进行了源码分析&#xff0c;该工具可根据用户的输入生成图片&#xff0c;由讯飞星火提供图片生成 API。通过本文学习可自行实现将第三方 API 封装为 Dify 中工具的能力。 源码位置&#xff1a;dify-0.6.14\api\core\tools\provide…

出厂非澎湃OS手机解BL锁

脚本作者&#xff1a;酷安mlgmxyysd 脚本项目链接&#xff1a;https://github.com/MlgmXyysd/Xiaomi-HyperOS-BootLoader-Bypass/ 参考 B站作者&#xff1a;蓝空穹 https://www.bilibili.com/read/cv33210124/ 其他参考&#xff1a;云墨清风、水墨青竹、Magisk中文网 决定解BL…

django学习入门系列之第十点《A 案例: 员工管理系统10》

文章目录 12 管理员操作12.4 密码加密12.5 获取对象&#xff08;防止id错误--编辑界面等&#xff09;12.6 编辑管理员12.7 重置密码 往期回顾 12 管理员操作 12.4 密码加密 密码不应该以明文的方式直接存储到数据库&#xff0c;应该加密才放进去 定义一个md5的方法&#xff…

js | TypeError: Cannot read properties of null (reading ‘indexOf’) 【解决】

js | TypeError: Cannot read properties of null (reading ‘indexOf’) 【解决】 描述 概述 在前端开发中&#xff0c;遇到TypeError: Cannot read properties of null (reading indexOf)这类错误并不罕见。这个错误通常表明你试图在一个null值上调用indexOf方法&#xff0c…

飞睿智能UWB BLE Tag蓝牙防丢器模块,APP测距定位一键绑定,安全守护每一刻

我们总在不经意间与生活中的小物件擦肩而过——钥匙遗忘在咖啡厅的角落&#xff0c;钱包遗失在拥挤的地铁&#xff0c;甚至孩子的书包在人群中悄然消失……每一次的失而复得都是幸运的眷顾&#xff0c;但更多的是遗憾与不便。今天&#xff0c;就让我带你走进一个智能守护的新世…

Linux驱动开发 ——架构体系

只读存储器&#xff08;ROM&#xff09; 1.作用 这是一种非易失性存储器&#xff0c;用于永久存储数据和程序。与随机存取存储器&#xff08;RAM&#xff09;不同&#xff0c;ROM中的数据在断电后不会丢失&#xff0c;通常用于存储固件和系统启动程序。它的内容在制造时或通过…

【算法】遗传算法

一、引言 遗传算法&#xff08;Genetic Algorithm, GA&#xff09;是一种模拟生物进化过程的启发式搜索算法&#xff0c;它通过模拟自然选择、遗传、交叉和突变等生物学机制来优化问题的解决方案。遗传算法因其通用性、高效性和鲁棒性&#xff0c;在多个领域中得到了广泛应用&a…

esp32核心跑分程序

https://github.com/ochrin/coremark/tree/esp32 最近一直捣腾esp32s3 (Sense) 做微型摄像。过程中发现一款不错的跑分软件&#xff0c;特此记一笔。 其中针对esp32s3各类参数设定&#xff08;用idf.py menuconfig)&#xff0c;做个记录。 CPU Frequency去240MHz&#xff08…

C语言 | Leetcode C语言题解之第413题等差数列划分

题目&#xff1a; 题解&#xff1a; int numberOfArithmeticSlices(int* nums, int numsSize) {if (numsSize 1) {return 0;}int d nums[0] - nums[1], t 0;int ans 0;// 因为等差数列的长度至少为 3&#xff0c;所以可以从 i2 开始枚举for (int i 2; i < numsSize; i…

Java | Leetcode Java题解之第415题字符串相加

题目&#xff1a; 题解&#xff1a; class Solution {public String addStrings(String num1, String num2) {int i num1.length() - 1, j num2.length() - 1, add 0;StringBuffer ans new StringBuffer();while (i > 0 || j > 0 || add ! 0) {int x i > 0 ? n…

通用四期ARM架构银河麒麟桌面操作系统V10【安装、配置FTP服务端】

一、操作环境 服务端&#xff1a;银河麒麟桌面操作系统V10SP1 &#xff08;服务端包链接&#xff1a;https://download.csdn.net/download/AirIT/89747026&#xff09; 客户端&#xff1a;银河麒麟桌面操作系统V10SP1 &#xff08;客户端包链接&#xff1a;https://downloa…

keil里sprintf的用法

代码&#xff1a; #include<stdio.h> int main(void) {float i-123.45;char zifu[10];sprintf(zifu,"%f",i);while(1);return 0; } 仿真结果 代码&#xff1a; #include<stdio.h> int main(void) {float i123.45;char zifu[10];sprintf(zifu,"%f…