【问题标题】:Is it possible to instruct C to not zero-initialize global arrays?是否可以指示 C 不对全局数组进行零初始化?
【发布时间】:2013-01-19 02:39:21
【问题描述】:

我正在编写一个嵌入式应用程序,我几乎所有的 RAM 都被全局字节数组使用。当我的固件启动时,它首先用零覆盖 RAM 中的整个 BSS 部分,这在我的情况下是完全没有必要的。

有什么方法可以指示编译器不需要对某些数组进行零初始化?我知道这也可以通过将它们声明为指针并使用 malloc() 来解决,但有几个原因我想避免这种情况。

【问题讨论】:

  • 其实不应该。通常链接器只保存 bss 部分的大小而不是它的(空)内容。固件只是清除所需的内存。如果不为阵列预留 RAM,您打算如何使用它们?
  • @FUZxxl 你是对的,我认为它是将零从闪存复制到 RAM,但我现在看到引导加载程序只是将等于 BSS 部分大小的零写入 RAM。这似乎仍然没有必要,因为我不依赖大多数数组进行零初始化,但它不会像我想象的那样影响性能。我编辑了我的问题以反映您的信息。
  • 我认为这取决于工具链。在我使用的工具链中,我可以指示链接器不初始化或加载部分。这实际上对于某些部分是强制性的(数据在多个内核之间共享)。
  • github.com/dwelch67 这是我的正常操作(不是零 bss,不是复制 .data)查找并更改您的引导代码(在 gnu 工具链世界中通常命名为 crt0.S)或者只是让您的拥有。

标签: c gcc embedded c99


【解决方案1】:

使用 gcc,-fno-zero-initialized-in-bss

【讨论】:

  • 这实际上是错误的。 -fno-zero-initialized-in-bss 选项意味着编译器将初始化为 0 的变量放入 .data 段,而不是像通常那样的 .bss 段。这是一个完全不同的问题。
【解决方案2】:

所有嵌入式编译器都应该允许一个 noinit 段。使用 IAR AVR 编译器,您不想被初始化的变量只需声明如下:

__no_init uint16_t foo;

这样做的最有用的原因是允许变量在看门狗或掉电复位时保持其值,这在基于计算机的 C 程序中当然不会发生,因此它从标准 C 中省略。

只需在编译器手册中搜索“noinit”或类似内容即可。

【讨论】:

    【解决方案3】:

    原来我的工具链中包含的链接器脚本有一个特殊的“noinit”部分。

    __attribute__ ((section (".noinit")))
    

    /** 强制编译器不自动将给定的全局归零 启动时变量,以便保留当前 RAM 内容。 在大多数情况下,该值将是随机的,因为 断电后易失性存储器的行为,但可以用于某些特定的 情况,例如在系统看门狗重置后传回值。

    因此,所有标有该属性的全局变量在启动期间都不会被初始化为零。

    【讨论】:

    • 但要小心,您需要将此属性添加到所有“全局变量”以及所有局部静态变量,还可能添加到具有不完整/部分初始化列表的普通本地数组/结构/联合(例如int arr[10] = {0})。最安全的方法是确保您永远不会在任何地方初始化变量,而是始终在运行时设置它们,如我的回答所示。
    • " 就像在系统看门狗重置后传回值一样。"- 最安全的方法是使用外部闪存,如果该地址处的值位于默认值(大多数闪存为 0xFF)并进行比较以确定是否发生了系统休息。
    【解决方案4】:

    有一些解决方法,例如:

    • 从二进制文件中删除 BSS 部分或将其大小设置为 0 或 1。如果加载程序必须为所有部分显式分配内存,这将不起作用。如果加载程序只是将数据复制到 RAM,这将起作用。
    • 在 C 代码中将数组声明为 extern 并在单独的汇编文件或链接描述文件中的汇编代码中定义符号(连同它们的地址)。同样,如果必须显式分配内存,这是行不通的。
    • 在加载程序中或在main() 之前在您的程序中执行的启动代码中修补或删除相关的 BSS 归零代码。

    【讨论】:

    • 在一些汇编程序 goo 中声明数组对我来说似乎是一个非常糟糕的设计理念。作为您的第三个选项,这样做会更好、更清晰:重写填充 .bss 和 .data 的初始化代码。当编译器不支持它时,我自己已经这样做了几次。但这假设您要么拥有裸机 CPU,要么拥有开源 RTOS。如果你不这样做,(假设你有嵌入式 Linux 或类似的),那么这种低级实时优化可能只是毫无意义和危险的。尽管根据我的经验,大多数编译器都可以选择删除初始化代码。
    • @Lundin 不管怎样,你必须准确地理解你在做什么。
    【解决方案5】:

    C 标准要求将全局数据初始化为零。

    某些嵌入式系统制造商可能会提供绕过此选项的方法,但如果“初始化为零”没有完成,肯定有许多典型的应用程序会失败。

    一些编译器还允许您有更多的部分,这些部分可能具有除“bss”部分之外的其他特征。

    另一种选择当然是“自己分配”。由于它是一个嵌入式系统,我想您可以控制应用程序和数据如何加载到 RAM 中,特别是使用哪些地址。

    因此,您可以使用指针,并简单地使用您自己的机制将指针分配给为您需要大型数组的任何内容保留的内存区域。这避免了 malloc 的相当复杂的用法 - 它为您提供了一个或多或少的永久地址,因此您不必担心以后试图找到您的数据在哪里。这当然会对性能产生很小的影响,因为它增加了另一个间接级别,但在大多数情况下,一旦数组用作函数的参数,它就会消失,因为无论如何它都会衰减到那个点的指针.

    【讨论】:

      【解决方案6】:

      问题在于标准 C 强制对静态对象进行零初始化。如果编译器跳过它,它将不符合 C 标准。

      在嵌入式系统编译器上,通常有一个非标准选项“紧凑启动”或类似选项。启用后,程序中的任何位置都不会发生静态/全局对象的初始化。如何执行此操作取决于您的编译器,或者在这种情况下,取决于您的 gcc 端口。

      如果您提及您使用的是哪个系统,那么有人可能会为该特定编译器端口提供解决方案。

      这意味着您显式初始化的任何静态/全局(静态存储持续时间)变量将不再被初始化。你必须在运行时初始化它,也就是说,你必须写static int x; x=1;而不是static int x=1;。以这种方式编写嵌入式 C 程序是很常见的,以使它们与禁用静态初始化的编译器兼容。

      【讨论】:

      • 零初始化位对我来说很有意义。在链接器脚本中用符号(如__bss_start__bss_end 等)包裹bss 的边界很简单,这些符号允许简单的循环遍历范围并写入零。但是,静态/全局变量如何使用它们的编译时常量值进行初始化?在用户空间的情况下,只要将.data 段复制到适当地址的内存中,它就可以工作是有意义的,但是这在嵌入式平台上如何工作?特别是使用objcopy创建bin文件的原因(例如objcopy -Obinary a.o a.bin
      • @sherrellbc 在嵌入式平台上,有代码(“CRT”)在调用 main() 之前运行,这会清除 .bss 并将值从闪存复制到 .data。大致类似于this example
      【解决方案7】:

      您确定二进制格式实际上在二进制文件中包含 BSS 部分吗?在我使用过的二进制格式中,BSS 只是一个整数,它告诉内核/加载器分配多少内存并将其归零。

      在 C 中肯定没有通用的方法来获取未初始化的全局变量。这将是您的编译器/链接器/运行时系统的一个功能,并且非常具体。

      【讨论】:

      • 你是对的,我认为它是将零从 FLASH 复制到 RAM,但我现在看到引导加载程序只是将等于 BSS 部分大小的零写入 RAM。这似乎仍然没有必要,因为我不依赖大多数数组进行零初始化,但它不会像我想象的那样影响性能。我编辑了我的问题以反映您的信息。
      猜你喜欢
      • 1970-01-01
      • 2020-07-18
      • 1970-01-01
      • 1970-01-01
      • 2014-04-06
      • 2010-09-08
      • 2017-09-06
      • 2020-03-30
      • 1970-01-01
      相关资源
      最近更新 更多