Избегание массивов стека переменной длины во время компиляции - PullRequest
2 голосов
/ 08 апреля 2019

Я реализовал функцию, которая требует некоторого временного стекового пространства, количество которого зависит от одного из его входов.Это пахнет как распределение памяти в стеке переменной длины, что не всегда считается хорошей идеей (например, оно не является частью C90 или C ++ и в этом контексте доступно только в gcc через расширение).Тем не менее, моя ситуация немного отличается: я знаю, сколько байтов я выделю во время компиляции, просто оно отличается для нескольких различных вызовов этой функции, разбросанных по моей кодовой базе.

C99Кажется, с этим все в порядке, но это не то, что, например, реализует Visual Studio, и, таким образом, мой CI работает на Windows не удается.

Кажется, у меня есть несколько вариантов, ни один из которых не является хорошим.Я надеюсь, что этот вопрос может либо убедить меня в одном из них, либо предоставить более идиоматическую альтернативу.

  • Выделить пространство стека вне вызова функции, основываясь на константе времени компиляции, которую я быв противном случае передать в качестве параметра, а затем передать указатель.
  • Превратить мою функцию в макрос.
  • Превратить мою функцию в макрос-обертку, который затем выделяет пространство стека и передает его в«настоящая» функция (по сути, объединяющая 1 и 2).
  • Каким-то образом убедите Visual Studio, что это нормально ( соответствующий NMakefile ).

Цель здесьне только для получения чего-то, что работает и является достаточно производительным, но и для того, чтобы было читабельным и чистым , поскольку это сильно согласуется с контекстом проекта, частью которого он является.Я должен отметить, что распределение в куче также не вариант здесь.

Как мне лучше всего с этим справиться?

Если вы предпочитаете больше практического, реального контекста, вот комментарий Github, где я описываю свой конкретный пример этой проблемы .

Ответы [ 2 ]

0 голосов
/ 08 апреля 2019

Очевидно, что 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 жалуется, или, по крайней мере, онлайн-компилятор, который я нашел для этого, жалуется.

0 голосов
/ 08 апреля 2019

Вы можете попробовать оба варианта:

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);
}
...