【问题标题】:How does C99 handle being unable to create a variable length array at run time?C99 如何处理无法在运行时创建可变长度数组?
【发布时间】:2017-05-17 10:53:09
【问题描述】:

我为嵌入式系统编程。一条黄金法则是我们从不致电malloc();所有数据必须在编译时静态分配。

因此,我对 Variable Length Arrays 不是很熟悉,它是在 C99 中引入的。

这个概念似乎很清楚,我不需要它解释。我的问题是,如果这样的数组没有足够的可用内存,在运行时会发生什么?

我想它依赖于 o/s,也许依赖于编译器,GCC/Linux 会做什么,以及 Windows 上的 MS Visual Studio C?任何 X99 或 Posix 定义?

【问题讨论】:

  • Mawg 我想你已经回答了你自己的问题。这将导致未定义的行为,因此您需要查看您开发的给定平台如何处理此问题。我猜你必须在调用使用 VLA 的函数之前尝试捕获它。我猜想如果要避免 malloc,VLA 也应该避免。
  • 适用与任何堆栈溢出相同的规则,可能会在较小的嵌入式 MCU 上无法控制地崩溃,并在较大的设备(包括 Linux/Windows)上通过内存保护进行捕获。更重要的是堆栈分配也是动态分配,因此在 C 中无法真正避免。如果您避免使用函数指针/ recursion/VLA/alloca(或者可能提供合适的手动提示,比如可能的函数指针目标列表)。
  • doynax,我在这里可能是错的,但在许多平台/环境中,堆栈在编译时每个进程都是固定的。例如,在 windows 上很容易用完堆栈(但这并不意味着它不是动态的,只是上限是静态的),但是说你可以通过启动一个新线程来解决这个问题任意堆栈空间。
  • @cdcdcd:是的,但仅限于可用内存。问题涉及嵌入式设备,因此这个空间可能相当有限,更糟糕的是,根据特定平台,可能会完全不受检查。通常要求是此类系统(您的烤面包机或其他任何东西)设计为永远不会耗尽内存,在这种情况下必须小心处理堆栈分配。名义上,相同的规则也适用于 Windows/Linux,但由于操作系统本身缺乏任何此类分配保证,因此您必须满足于尽力而为的解决方案。
  • VLA 通常(取决于编译器)从堆栈分配内存,类似于 alloca() 或 _alloca(),在这种情况下没有堆或 malloc() 不是问题。在嵌入式系统上,堆栈空间(每个线程)可能非常有限。

标签: c c99 variable-length-array


【解决方案1】:

从标准的角度来看,尝试分配具有实现无法容纳的大小的 VLA 会调用未定义的行为。因为标准没有提供发现实现可以安全创建的大小数组的方法,并且不强制实现允许任何特定大小,所以任何创建大小大于 1 的 VLA 对象的尝试都应视为调用未定义行为,除非在碰巧对实现的内部工作有足够了解以确定它能够处理的 VLA 大小的情况。

如果 malloc() 不可用,最好的办法是定义一个大数组 无论哪种类型具有最粗略的对齐要求,都将其地址存储到 一个volatile-qualified 指针[指针本身所在的存储 应该因此被限定]读回它,并将其解释为一个开始 内存池。不应对原始数组对象进行其他用途。尽管 标准不保证编译器不会决定它应该生成检查指针是否仍然标识原始对象的代码,如果确实如此,则跳过任何会使用该指针访问原始对象以外的任何内容的代码类型,在指针上使用volatile 应该使这种情况不太可能发生。

创建内存池后,您可以编写自己的内存管理 使用它的函数,尽管任何时候指针返回到池中 可能需要使用volatile-pointer-laundering hack 来防止 编译器使用基于类型的别名来证明处理最后一次使用是合理的 将存储作为其旧类型,相对于第一次使用而言是无序的 存储作为一种新类型。

【讨论】:

  • 所有数据必须在编译时静态分配的意思是“所有数据必须在编译时静态分配”。不是“只要您使用自己的函数,您就可以在运行时分配自己的数据”。
  • @n.m.:数组将在编译时分配;用户函数只会返回指向已分配存储部分的指针。
  • “分配”并不意味着“向操作系统、编译器或上帝寻求任何资源”。它不一定是单层事务。编译器为你的程序分配一块内存,(静态),然后你的内存管理器使用这个块来分配更小的块(动态)
  • supercat 您基本上是在说如果您想要一种动态分配类型,请不要使用 VLA 或 malloc(它们不安全)。而是静态分配您知道在编译时安全的资源(您知道平台限制),然后为此资源编写您自己的“TLB”版本并进行管理。
  • 谁承诺过 1 号一定会成功?我不会相信他。
【解决方案2】:

可变长度数组通常分配在堆栈上。与分配在堆栈上的任何其他变量一样,这通常通过从堆栈指针中减去(或添加到堆栈指针以实现向上增长的堆栈)来完成。可能会使用帧指针,以便函数可以在面对堆栈指针的动态确定更改时跟踪其堆栈帧。与其他堆栈分配一样,此过程中通常没有错误检查。

这会带来一些危险。

  1. 分配给堆栈的空间可能溢出。根据平台的不同,这可能会导致内核出现某种内存错误,可能会导致平台动态分配更多的堆栈空间,或者可能会导致覆盖其他内存。
  2. 在使用超出堆栈的保护页面来实现自动堆栈增长和/或检测堆栈溢出的平台上,足够大的堆栈分配可能会“跳过”这些页面。这可能会导致内存保护错误或更严重的内存损坏,如果堆栈溢出通常会被捕获。

这些风险都不是特定于可变长度数组,但可变长度数组使它们更有可能隐藏起来,直到使用特定参数调用代码。

【讨论】:

    猜你喜欢
    • 2012-12-04
    • 1970-01-01
    • 2012-01-29
    • 2011-12-13
    • 1970-01-01
    • 2021-12-15
    • 2017-04-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多