在编译时避免变长堆栈数组

Posted

技术标签:

【中文标题】在编译时避免变长堆栈数组【英文标题】:Avoiding variable-length stack arrays at compiletime 【发布时间】:2019-04-08 11:27:16 【问题描述】:

我已经实现了一个需要一些临时堆栈空间的函数,其数量取决于它的一个输入。这闻起来像可变长度堆栈内存分配,这并不总是被认为是一个好主意(例如,它不是 C90 或 C++ 的一部分,并且在这种情况下,只能通过扩展在 gcc 中使用)。但是,我的情况略有不同:我确实知道在编译时最终会分配多少字节,只是对这个函数的几次不同调用不同,分散在我的代码库周围。

C99 似乎对此很好,但这不是例如Visual Studio 实现了,因此我在 Windows 上运行的 CI 失败了。

似乎我有几个选择,但都不是很好。我希望这个问题可以让我相信其中的一个,或者提供一个更惯用的替代方案。

根据编译时常量分配函数调用之外的堆栈空间,否则我将作为参数传递,然后传递一个指针。 把我的函数变成一个宏。 将我的函数转换为包装宏,然后分配堆栈空间并将其传递给“真正的”函数(基本上结合了 1 和 2)。 以某种方式让 Visual Studio 相信这很好 (relevant NMakefile)。

这里的目标不仅是获得工作且性能合理的东西,而且可读且干净,因为这与项目的上下文高度一致这是一部分。我应该注意,堆上的分配在这里也不是一个选项。

我怎样才能最好地处理这个问题?

如果您更喜欢亲身实践的真实环境,请here's a Github comment where I describe my specific instance of this problem。

【问题讨论】:

这闻起来像动态堆栈内存分配,这是个坏主意。 哦?为什么这是一个“坏主意”?那为什么栈存在呢? 我假设动态内存分配是不可能的?如果是这样,您的第一个选项将是最有意义的。 @AndrewHenle 关于这个问题的讨论无休止;例如,它是标准 C99,但不是 C++11 的一部分,尽管许多编译器确实支持它作为扩展。我将重新制定以避免争议。 @JohnBode 啊,是的,这是相关的上下文。谢谢! 也许 MSVC 在这里是的东西。 【参考方案1】:

显然 MSVC 确实处理 C99 复合文字(第 6.5.2.5 节),因此您可以将堆栈分配的数组作为附加参数直接传递给被调用的函数。您可能希望使用宏来简化调用语法。

这是一个例子:

/* Function which needs two temporary arrays. Both arrays and the size
 * are passed as arguments
 */
int process(const char* data, size_t n_elems, char* stack, int* height) 
  /* do the work */


/* To call the function with n_elems known at compile-time */
int result = process(data, N, (char[N])0, (int[N])0);

/* Or you might use a macro like this: */
#define process_FIXED(D, N) (process(D, N, (char[N])0, (int[N])0)))
int result = process_FIXED(data, N);

process 函数不需要知道临时对象是如何分配的;调用者也可以 malloc 数组(并在调用后释放它们)或使用 VLA 或 alloca 对它们进行堆栈分配。

复合文字被初始化。但是它们不能太大,否则你会冒堆栈溢出的风险,所以开销不应该过多。但这是你的决定。请注意,在 C 中,初始化列表不能为空,尽管 GCC 似乎毫无怨言地接受了(char[N])。 MSVC 抱怨,或者至少我为它找到的在线编译器抱怨。

【讨论】:

【参考方案2】:

你可以尝试同时提供:

module.h

// Helper macro for calculating correct buffer size
#define CALC_SIZE(quantity)  (/* expands to integer constant expression */)

// C90 compatible function
void func(uint8_t * data, int quantity);

// Optional function for newer compilers
// uses CALC_SIZE internally for simpler API similarly to 'userFunc' below
#if NOT_ANCIENT_COMPILER
void funcVLA(int quantity);
#endif

user.c

#include "module.h"
void userFunc(void) 
    uint8_t buffer[CALC_SIZE(MY_QUANTITY)];
    func(buffer, MY_QUANTITY);

【讨论】:

以上是关于在编译时避免变长堆栈数组的主要内容,如果未能解决你的问题,请参考以下文章

GCC 如何实现变长数组?

关于如何定义一个未知大小的数组

堆栈动态和堆栈动态数组

C 数组实例化 - 堆栈或堆分配?

C中堆栈上的动态数组分配

在编译时检查堆栈使用情况