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

时间:2013-03-17 02:54:16

标签: c assembly embedded

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

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

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

memcpy

......而且效果很好。节省空间可以忽略不计,但现在显然已经很简单了。

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

    无论如何,
  • /* 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); */ memcpy很可能在可执行文件中被链接,因为程序员会直接使用它,或间接地通过另一个库使用它;
  • 它更小;
  • 速度对于启动代码来说不是一个非常重要的因素,但它可能更快;
  • 几乎不可能弄错。

知道为什么人们不会依赖memsetmemcpy来启动代码?

3 个答案:

答案 0 :(得分:21)

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

答案 1 :(得分:15)

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

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

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

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

答案 2 :(得分:1)

我不认为这可能与“关于memcy / memset的内部状态的假设”有关,它们不太可能使用任何全局资源(尽管我认为存在一些奇怪的情况)。

微控制器上的所有启动代码通常都是以这种方式编写的“内联汇编程序”,因为它在代码的早期阶段运行,其中堆栈可能尚未存在且MMU设置可能尚未执行。因此,初始化代码不希望冒任何问题放在堆栈上,这很简单。函数调用将事物放在堆栈中。

因此,虽然这恰好是静态存储副本的初始化代码,但您也可能在其他此类初始化代码中找到相同的内联汇编程序。例如,您可能会在复制之前找到一些用汇编程序编写的基本寄存器设置代码,您也可以在汇编程序中找到MMU设置。