嵌入式:大多数CRT启动代码都没有使用memcpy/memset - 为什么?

Jar*_*der 16 c embedded assembly

背景:
我正在研究ARM目标,更具体地说是ST的Cortex-M4F微控制器.在这样的平台(一般的微控制器)上工作时,显然没有操作系统; 为了获得一个有效的C/C++"环境"(此外,在变量初始化方面符合标准),必须在重置时运行某种启动代码,在显式调用之前执行所需的最小设置main.正如我所暗示的,这样的启动代码必须初始化初始化的全局和静态变量(例如int foo = 42;在全局范围内)并将其他全局变量(例如全局范围)清零int bar;.然后,如果需要,调用全局"ctors".

在微控制器上,这只是意味着启动代码必须为每个初始化的全局(全部在".data"部分中)将数据从闪存复制到ram,并清除其他部分(全部在'.bss'中).因为我使用GCC,我必须提供这样的启动代码,我很高兴地分析了几个启动代码(及其相关的链接器脚本!),这些代码与我在互联网上找到的大量示例捆绑在一起,所有这些都使用我正在开发的相同演示板.

问:
正如所述,我已经看到了许多启动代码,它们以不同的方式初始化全局变量,在空间和时间方面比其他方式更有效.但他们都有一个共同点奇怪:他们没有使用memset也没有memcpy,而不是诉诸于手写的循环来完成这项工作.由于我觉得在可能的情况下使用标准函数(简单的"DRY原理")似乎很自然,我尝试了以下代替最初的手写循环:

/* Initialize .data section */
ldr r0, DATA_LOAD
ldr r1, DATA_START
ldr r2, DATA_SIZE
bl  memcpy       /* memcpy(DATA_LOAD, DATA_START, DATA_SIZE); */

/* Initialize .bss section */
ldr r0, BSS_START
mov r1, #0
ldr r2, BSS_SIZE
bl  memset       /* memset(BSS_START, 0, BSS_SIZE); */
Run Code Online (Sandbox Code Playgroud)

......它完美无缺.节省空间可以忽略不计,但现在显然已经很简单了.

所以,我想到了,在这种情况下,我认为没有理由做手写循环:

  • memcpy并且memset很可能无论如何都要链接在可执行文件中,因为程序员会直接使用它,或间接地通过另一个库使用它;
  • 它更小;
  • 速度对于启动代码来说不是一个非常重要的因素,但它可能更快;
  • 几乎不可能弄错.

知道为什么一个人不会依赖memcpymemset启动代码?

R..*_*R.. 21

我怀疑启动代码不想对memcpylibc中的实现做出假设.例如,memcpy可以使用由libc初始化代码设置的全局变量来报告哪些cpu扩展可用,以便在支持此类操作的机器上提供优化的SIMD复制.在早期的"crt"启动代码运行时,这种全局的存储可能完全未初始化(包含随机垃圾),在这种情况下调用会很危险memcpy.即使打电话给你工作,也是实施的结果(或者甚至UB的不可预测的结果......)使它工作; 这可能不是crt代码想要依赖的东西.

  • 对!或者,`memcpy`或`memset`可能会使用懒惰启用的SIMD/FPU指令.在启动初期,是否有处理器来处理首次使用此类指令时将发生的故障?上下文保存了故障处理程序需要写入的区域吗? (2认同)
  • 还有其他可能的问题 - MMU或CPU可能尚未配置为支持未对齐的访问或`libc`例程使用的传输宽度等.有很多非常好的理由只使用您直接控制的代码在创业初期. (2认同)

Cli*_*ord 15

标准库是否完全链接是应用程序开发人员的决定(--nostdlib例如可能使用),但是启动代码是必需的,因此它不能做出任何假设.

此外,启动代码的目的是建立一个可以运行C代码的环境; 在完成之前,任何可能合理地假设完整运行时环境的库代码都无法正确运行.对于有问题的功能,这在许多情况下可能不是问题,但你不可能知道.

启动代码必须至少建立一个堆栈并初始化静态数据,在C++中它还会调用全局静态对象的构造函数.标准库可能合理地假设这些已经建立,因此在此之前使用标准库可能会导致错误的行为.

最后,您应该清楚C语言和C标准库是不同的实体.语言必须能够独立存在.