# 启动文件的几个问题的分析

以下分析如无特别指出启动文件，均是指官方hal库启动文件startup\_stm32f103xe.s

## 为什么编译器编译的时候能够把启动文件的代码指令放在bin文件的最前面？

> 因为.sct链接脚本，声明了编译的规则，也就是如下的代码段中，.o (RESET, +First)，**section是一种将代码、数据和其他相关信息组织在一起的方式。在链接时，它们被放置在特定的内存地址中。而.o (RESET, +First) 是指定义了一个名为“RESET”的section，并将其放置在链接脚本的第一个位置**，而这个段就在启动文件中，实际上指的就是中断向量表，第一个指令是 DCD \_\_initial\_sp，DCD是伪指令，会分配一个或者多个以字为单位的内存，以四字节对齐，并要求初始化这些内存。也就是将\_\_initial\_sp写入内存， 而此时所在的内存位置正是编译生成的第一个位置。
>
> 如果使用了该.sct文件而不存在AREA RESET, DATA, READONLY，会编译失败
>
> ```
>             AREA    RESET, DATA, READONLY
>             EXPORT  __Vectors
>             EXPORT  __Vectors_End
>             EXPORT  __Vectors_Size
> ```

```
LR_IROM1 0x08000000 0x01000000  {    ; load region size_region
  ER_IROM1 0x08000000 0x01000000  {  ; load address = execution address
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
  }
  RW_IRAM1 0x20000000 0x00010000  {  ; RW data
   .ANY (+RW +ZI)
  }
}
```

## 如果在汇编的启动文件中不引入\_main函数那么程序能够正常执行吗？

> 可以如果主函数命名为main，那么编译器会自动生成一个名为 \_main 的函数，并将其作为 main 函数的入口点，也就是说先执行\_main再执行main,实际上，\_main 函数是由 C 库提供的，它会在程序启动时执行一些初始化操作，然后调用用户定义的 main 函数，并在 main 函数返回后执行一些清理操作。 总之，在使用标准的 C 库时，main 函数会默认调用 \_main 函数，用户无需关心 \_main 函数的具体实现，只需要在 main 函数中编写自己的代码即可。
>
> main.c如下
>
> ```
> #include "uart.h"
>
> void delay(int d)
> {
>     while(d--);
> }
>
> int main()
> {
>     char c = 'A';
>
>     while (1)
>     {
>         putchar(c++);
>         delay(1000000);
>         if (c == 'Z')
>             c = 'A';
>     }
>
>     return 0;
> }
> ```
>
> 启动代码如下
>
> ```
>                 PRESERVE8
>                 THUMB
>
>
> ; Vector Table Mapped to Address 0 at Reset
>                 AREA    RESET, DATA, READONLY
>                 EXPORT  __Vectors
>
> __Vectors       DCD     0                  
>                 DCD     Reset_Handler              ; Reset Handler
>
>                 AREA    |.text|, CODE, READONLY
>
> ; Reset handler
> Reset_Handler   PROC
>                 EXPORT  Reset_Handler             [WEAK]
>                 IMPORT  main
>
>                 LDR SP, =(0x20000000+0x10000)
>                 BL main
>
>                 ENDP
>
>                  END
> ```
>
> 最后的MAP文件如下，可以发现多了一个从未使用过的!!!main 数据段，来自\_\_main.o文件
>
> <img src="https://s2.loli.net/2023
> /03/05/PetMjXpg6W4GlND.png" alt="" data-size="original">
>
> 如果将main.c的main函数与汇编文件的IMPORT main的main都修改为mymain,
>
> main.c如下
>
> ```
> #include "uart.h"
>
> void delay(int d)
> {
>     while(d--);
> }
>
> int mymain()
> {
>     char c = 'A';
>
>     while (1)
>     {
>         putchar(c++);
>         delay(1000000);
>         if (c == 'Z')
>             c = 'A';
>     }
>
>     return 0;
> }
> ```
>
> 启动文件如下
>
> ```
>                 PRESERVE8
>                 THUMB
>
>
> ; Vector Table Mapped to Address 0 at Reset
> ;                AREA    RESET, DATA, READONLY
> ;                EXPORT  __Vectors
> ;                    
> ;__Vectors       DCD     0                  
> ;                DCD     Reset_Handler              ; Reset Handler
> ;
>                 AREA    |.text|, CODE, READONLY
>
> ; Reset handler
> Reset_Handler   PROC
>                 EXPORT  Reset_Handler             [WEAK]
>                 IMPORT  mymain
>
>                 LDR SP, =(0x20000000+0x10000)  ; 0x08040000
>                 BL mymain
>
>                 ENDP
>
>                  END
> ```
>
> 最后的map如下
>
> <img src="https://s2.loli.net/2023/03/05/DFiq4lZUtzTXonc.png" alt="" data-size="original">
>
> 可以发现此时就不会调用\_\_\_\_main了
>
> 总结:存在main函数，会自动调用\_\_main，在执行完\_\_main后在执行main

## 为什么已经使用了SPACE后面还使\_user\_initial\_stackheap 来初始化堆栈

> 在代码中，\_\_user\_initial\_stackheap 是一个符号，它指向堆栈和堆空间的初始化函数。在初始化堆栈和堆空间时，程序会调用该函数来完成初始化。 在启动文件中，使用 SPACE 指令开辟了一段连续的内存空间，但这些空间并没有被初始化。因此，在程序运行时，需要调用 \_user\_initial\_stackheap 函数来初始化堆栈和堆空间。该函数会将堆空间和堆栈的起始地址和大小传递给运行时库，然后运行时库会在该地址范围内分配堆空间和堆栈，并进行必要的初始化操作。 因此，使用 SPACE 指令在启动文件中开辟内存空间只是为了定义堆栈和堆空间的位置和大小，而初始化堆栈和堆空间的工作需要通过调用 \_user\_initial\_stackheap 函数来完成。
>
> 该函数会被\_main调用，这里的初始化起始应该就是只把数据搬移到指定的RAM空间把，虽然这里的数据都是0

&#x20;

## 不是已经有SPACE指令开辟堆栈空间了吗，\_\_user\_initial\_stackheap是做什么的？

> SPACE只是在bin文件中生成了对应的数据段，在烧录时会被写入到对应的FLASH空间上，但是实际上并没有对RAM进行操作，操作RAM加载与初始化堆栈空间是由\_\_user\_initial\_stackheap函数实现的，设置完RAM后，我们就可以用，可以使用malloc去使用堆空间，函数使用的栈空间也得已使用,
>
> 据网上的资料显示[mdk 启动代码 \_\_user\_initial\_stackheap()解析](https://blog.csdn.net/wangfoquan/article/details/7648668/?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-0--blog-6634624.pc_relevant_3mothn_strategy_and_data_recovery\&spm=1001.2101.3001.4242.1\&utm_relevant_index=3)，**使用分散加载文件，启动代码里一定要使用\_\_user\_initial\_stackheap()来重新设置堆栈和堆，（但好像其实不用也没事）实际上也就是将以下代码的RW 与ZI去加载到对应空间，查看MAP文件的Execution Region RW\_IRAM1信息就可以发现需要加载的数据到哪些地址**，具体查看散列文件章节
>
> ```
>  RW_IRAM1 0x20000000 0x00010000  {  ; RW data
>    .ANY (+RW +ZI)
>   }
> }
> ```

## \_\_user\_initial\_stackheap 会在哪里调用

> 会被c库函数\_main调用，代替C库函数默认的堆栈和堆初始化函数，具体流程如下

![](<https://s2.loli.net/202&#xA;3/03/05/KLcpePgqR9nZiXF.png>)

## 是否使用\_\_MICROLIB，带来的两种堆栈初始化的差异是什么

不定义\_\_MICROLIB也就是不勾选Use MicroLIB的话，会使用\_\_user\_initial\_stackheap去初始化堆栈空间，该函数会被\_main调用，生成的map如下，可以发现使用了\_\_main.o的!!main函数（实际就是\_\_main函数）

![](<https://s2.loli.net/2023/03/05/Zu&#xA;s9RqpHPV7dhO2.png>)

勾选上Use MicroLIB的话，会发现生成的map里调用的是entry.o，段名也不是!!!main了，说明初始化的流程不一样了，而且往下寻找也没有发现!!main了，但实际上在汇编中还是有执行\_\_main，而且主函数叫做main，理论上也会调用 \_\_main函数，推测如下

在使用MicroLIB库的情况下，编译器和链接器会使用另外的启动文件和链接脚本，与标准C库的启动文件和链接脚本不同。在MicroLIB库的启动文件中，会定义一个名为\_\_main的函数，它的作用类似于标准C库中的main函数，用于C语言程序的初始化和入口，

并且在尝试过将\_\_initial\_sp，\_\_heap\_base，\_\_heap\_limit三个值删掉后，并且主函数为main后，会报错，但若是修改main为其他名字，会发现就算将这些值删掉后，也不会报错，这样看来也像是因为用了main，所以调用了\_\_main,所以报错。

但是该推断有一问题是为什么段名也没有出现\_\_main

![](<https://s2.loli.net/2023/03/0&#xA;5/l5Xgy8vkpdneOso.png>)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://oceanaparts.gitbook.io/halnote/qi-dong-wen-jian-de-ji-ge-wen-ti-de-fen-xi.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
