【问题标题】:C program with minimum RAM具有最小 RAM 的 C 程序
【发布时间】:2013-11-21 21:05:53
【问题描述】:

我想了解用于应用程序开发的 C 和 C++ 编程中的内存管理。应用程序将在 PC 上运行。

如果我想让一个程序在运行时尽可能少地使用RAM,那么在编程时我需要考虑哪些点?

根据我的理解,这里有两点,但我不确定:

(1) 在 main() 和其他函数中使用最少的局部变量。 由于局部变量保存在堆栈中,即RAM?

(2) 在顶部使用全局变量而不是局部变量。 由于全局变量保存在未初始化和已初始化的ROM区?

谢谢。

【问题讨论】:

  • 这根本不是您应该担心的。使用高效的数据结构,避免冗余,学习一些算法。
  • PC 的 ROM 空间在哪里? (不包括 BIOS。)
  • @BitFiddlingCodeMonkey,这是不正确的。全局变量也存储在 RAM 中(在 PC 中)。只是不同的部分而不是堆栈。
  • @Shahbaz 我是一个嵌入式的人。对不起。 :-(
  • 你愿意用汇编编写而不是在 C 运行时库中链接吗?这消除了在实际程序入口点和main 之间运行的所有代码,以及它设置的数据,以便库例程工作。您可能会发现以这种方式编程有点不方便。更激进的技术是一次只在内存中保留少量数据,仅足以执行一个基本操作,并在磁盘上读取和写入其他数据。这将使您的程序变慢,并且只会在极端情况下使用。

标签: c++ c


【解决方案1】:

1) 通常,在堆栈上分配的替代方法是在堆上分配(例如,使用malloc),由于簿记/等原因实际上具有更大的开销,并且堆栈已经为其保留了内存,因此分配在可能的情况下放在堆栈上通常是可取的。另一方面,堆栈上的空间较少,而在具有虚拟内存和 64 位地址空间的现代系统上,堆可以接近“无限”。

2) 在 PC 和其他非嵌入式系统上,程序中的所有内容都在 RAM 中,也就是说,它不会闪存到类似 ROM 的内存中,因此全局与本地在这方面没有帮助。此外,只要应用程序正在运行,全局变量†往往会“存活”,而局部变量可以根据需要分配和释放(在堆栈或堆上),因此更可取。

† 更准确地说,还可以有具有static 持续时间的局部变量,以及具有全局范围的变量,它们是指向动态分配的内存的指针,因此这里使用的术语局部和全局非常松散。


一般来说,现代台式机/笔记本电脑甚至移动操作系统都非常擅长管理内存,因此您可能不应该尝试对所有内容进行微优化,因为实际上弊大于利。

如果您确实需要减少程序的内存占用,您必须意识到程序中的所有内容都存储在 RAM 中,因此您需要努力减少数量和大小你拥有的东西,而不是试图玩弄它们的位置。您可以在 PC 上本地存储内容的另一个地方是硬盘驱动器,因此在此处存储大量资源并仅根据需要加载它们(最好只加载所需的部分)。但请记住,磁盘访问比内存访问慢几个数量级,并且如果内存已满,操作系统也可以将内容交换到磁盘。

程序代码本身也存储在 RAM 中,因此请让您的编译器针对大小进行优化(许多常见编译器中的-Os 或/Os 选项)。还要记住,如果您通过编写更复杂的代码来节省变量空间,那么增加的代码大小可能会使您的努力付诸东流;保存您的优化以获得大胜利(例如,压缩大量资源将需要添加解压缩代码,但仍可能产生大的净胜利)。如果同时运行的多个程序使用同一个库,则使用动态链接库(和其他资源)也有助于整体系统的内存占用。

(请注意,上述某些内容不适用于嵌入式开发,例如,代码和静态常量可能确实存储在闪存中而不是 RAM 中等)

【讨论】:

  • 变量的位置并不重要,因为变量无论如何都占用RAM。
  • +1 第二点,如果现代系统上的数据段没有加载到 RAM 上,性能将是灾难性的。
  • @ThomasMatthews 如果您指的是堆栈与堆,那么与堆栈相比,在堆上分配确实会产生额外的开销,就像我提到的那样(但在非嵌入式系统中微不足道)。如果您指的是本地与全球,那么就像我提到的那样,生命周期是不同的。
  • 我指的是一个 4k 的数组将占用 4k 的 RAM,无论它是驻留在堆栈还是堆上,还是在“全局”空间中。仍然需要考虑 4k 的额外 RAM。
  • 在很多系统上,堆栈是固定大小的,除了堆栈之外其他任何东西都不能使用,所以你不妨在堆分配上使用它......
【解决方案2】:

这很困难,因为在您的 PC 上,程序将耗尽 RAM,除非您能以某种方式从 ROM 或闪存中执行它。

以下是需要考虑的要点:

减少代码大小。
代码占用 RAM。

减少可变数量和大小。
变量需要存在于某个地方,并且某个地方在 RAM 中。

减少字符字面量。
它们也占用空间。

减少函数调用嵌套。
一个函数可能需要参数,这些参数放在 RAM 中。 调用其他函数的函数需要返回路径;路径存储在 RAM 中。

使用其他设备的 RAM。
其他设备,例如图形处理器和您的硬盘驱动适配卡,可能有您可以使用的 RAM。如果您使用此 RAM,则您没有使用主 RAM。

页面内存到外部设备。
操作系统具有虚拟内存的能力,并且可以将内存分页到外部设备,例如硬盘驱动器。

编辑 1 - 动态库 太减少程序的 RAM 占用空间,您可以分配一个交换库函数的区域。这类似于 DLL 的概念。当需要某个功能时,您可以将其从硬盘驱动器加载到保留区域。

【讨论】:

    【解决方案3】:

    通常会为堆栈分配一定的空间;无论是否使用,此类空间都将不可用于其他目的。如果空间不足,程序将惨死。

    将使用寄存器和堆栈空间的某种组合来存储局部变量。一些编译器将使用相同的寄存器或堆栈空间来存储在程序执行的不同时间“活动”的变量;其他人不会。此外,函数参数通常在调用函数之前被压入堆栈,并在调用者方便时删除。在评估代码序列时:

    function1(1,2,3,4,5);
    function2(6,7,8,9,10);
    

    第一个函数的参数将被压入堆栈并调用该函数。此时编译器可以从堆栈中删除这五个值,但是由于一条指令可以删除任意数量的推送值,因此许多编译器将推送第二个函数的参数(将第一个函数的参数留在堆栈上),调用第二个函数,然后用一条指令消除所有十个。通常这不是问题,但在某些深度嵌套的递归场景中,它可能会成为问题。

    除非按照今天的标准,您正在开发的“PC”很小,否则我不会太担心尝试对 RAM 使用进行微优化。我已经为只有 25 字节 RAM 的微控制器开发了代码,甚至编写了成熟的游戏,用于在基于微处理器的控制台上使用,具有高达 128 字节(不是千字节!)的 RAM,并且在这样的系统上坐下来是有意义的担心每个单独的字节。但是,对于 PC 应用程序,唯一需要担心单个字节的情况是它们是数据结构的一部分,该数据结构将在 RAM 中被复制数千次。

    【讨论】:

    • 可以使用零堆栈空间或最小化堆栈空间创建程序。没有神奇的最小堆栈空间。该程序负责在执行main()函数之前设置它的环境。
    • @ThomasMatthews:在 PC 上,操作系统通常会施加最小堆栈要求。即使在嵌入式环境中,部分或全部中断处理将与其他所有中断处理共享相同的堆栈,如果没有足够的堆栈可用于中断处理程序,就会发生不好的事情。
    【解决方案4】:

    您可能想要一本关于“嵌入式”编程的书。这样的书可能会讨论减少内存占用的方法,因为嵌入式系统比现代桌面或服务器系统更受限制。

    当您使用“本地”变量时,它们会保存在堆栈中。只要你不使用太多堆栈,这基本上是空闲内存,因为当函数退出时,内存被返回。多少是“太多”变化......最近我不得不在一个系统上工作,每个进程的堆栈数据限制为 8 KB。

    当您使用“全局”变量或其他静态变量时,您使用的内存会在程序运行期间被占用。因此,您应该尽量减少对全局变量的使用,和/或想办法在程序中的多个函数之间共享相同的内存。

    我为几年前写的一个项目写了一个相当精细的“对象管理器”。函数可以使用“get”操作来借用对象,然后在借用对象完成后使用“release”操作。这意味着系统中的所有功能都能够通过轮流使用共享对象来共享相对少量的数据空间。由您决定是否值得花时间构建“对象管理器”之类的东西,或者您是否有足够的内存来使用简单的变量。

    您可以通过简单地调用malloc() 和free() 获得很多“对象管理器”的好处。然后堆分配器为你管理共享资源,堆内存。我编写自己的“对象管理器”的原因是对速度的需求。我的系统一直使用相同的数据对象,并且继续重复使用相同的数据对象比继续释放它们并再次分配它们要快得多。另外,我的系统可以在 DSP 芯片上运行,malloc() 在某些 DSP 架构上可能是一个非常慢的函数。

    如果一个函数试图保持全局缓冲区,而另一个函数正在覆盖数据,那么使用相同全局变量的多个函数可能会导致您遇到棘手的错误。因此,如果您使用malloc() 和free(),只要每个函数只写入它为自己分配的数据,您的程序可能会更健壮。 (但是malloc() 和free() 可能会引入它们自己的错误:内存泄漏、双重释放错误、在指针指向的数据被释放后继续使用指针......如果你使用malloc() 和@ 987654329@一定要用Valgrind之类的工具来检查你的代码。)

    【讨论】:

    • 嵌入式系统受到限制或具有比 PC 更严格的限制。这些限制之一通常是 RAM。 PC可以并且确实将RAM分页到硬盘驱动器;而大多数嵌入式系统没有硬盘。
    • 是的,我当然同意,这就是为什么我建议在一本关于嵌入式开发的书中包含有关降低 RAM 使用率的技巧。
    【解决方案5】:

    根据定义,任何变量都必须存储在读/写内存(或 RAM)中。如果您谈论的是最初在 ROM 中的代码的嵌入式系统,那么运行时会将您识别的 ROM 映像复制到 RAM 中以保存全局变量的值。

    只有标记为不可更改 (const) 的项目才能在运行时保留在 ROM 中。

    此外,您需要减少程序调用结构的深度,因为每个函数调用都需要堆栈空间(也在 RAM 中)来记录返回地址和其他值。

    为了尽量减少内存使用,您可以尝试使用 register 属性标记局部变量,但这可能不会被您的编译器支持。

    另一种常见的技术是在需要时动态生成大型可变数据以避免必须创建缓冲区。这些通常比简单变量占用更多空间。

    【讨论】:

    • 他指定它将在 PC 上运行
    • 不要使用 register 关键字。您很可能不知道程序将如何编译,即编译器在程序运行之前会对程序进行哪些优化,等等。变量被赋值为 auto 无论如何。充其量,编译器会忽略它,最坏的情况是,您会使程序运行效率低于其他情况。 drdobbs.com/keywords-that-arent-or-comments-by-anoth/184403859 看一下,它提到了register 以及其他一些关键字......
    • 大家说的这个ROM在哪里? PC 主要是基于 RAM 的架构。程序和数据被加载到 RAM 中。常量和可变变量被加载到 RAM 中。
    • @user2366842:register 关键字有什么问题?我用它来提醒编译器使用寄存器而不是每次访问都从内存中获取。我发现当优化启动时,我使用的编译器只会忽略register 建议。
    • 这取决于您的环境,因为这被声明为 PC 目标,我认为这充其量是矫枉过正。另外,正如我所说,您并不真正知道您的编译器将为您做什么样的优化(如果它发现变量在程序中被频繁使用,它甚至可能会自行注册变量)。
    【解决方案6】:

    如果这是一台 PC,那么默认情况下,您将获得一个特定大小的堆栈(您可以将其变大或变小)。使用此堆栈比使用全局变量更有效的 RAM。因为您的 ram 使用量将是固定堆栈大小 + 全局变量 + 其他东西(程序、堆等)。堆栈充当可重复使用的内存。

    【讨论】:

    • 请原谅我的无知,但是堆栈如何比堆或自动/全局变量区域更有效?堆不是一块可重用的内存(就此而言,不是全部或 RAM)吗?
    • 堆是可重用的,全局的,一旦存在就一直存在,栈和堆变量来来去去,所以同一个ram被复用。堆不像栈那样细粒度,它是按块分配的。
    • 智能内存分配器有一个用于细粒度分配的池和其他用于粗粒度分配的池。
    猜你喜欢
    • 2023-03-05
    • 2012-06-08
    • 2012-10-06
    • 2018-10-22
    • 2015-02-13
    • 2010-12-22
    • 1970-01-01
    • 2015-08-27
    相关资源
    最近更新 更多