"variable" 使用 C 预处理器进行长度初始化

"variable" length initialisation using C preprocessor

我正在为 stm32l0 微控制器编写一些程序。 我将一些数组定义为全局变量:

volatile uint32_t     synth_counter  [N_CHANNELS];
volatile uint32_t     synth_pitch    [N_CHANNELS];
volatile uint32_t     synth_envelope [N_CHANNELS];
volatile int_fast8_t  synth_key      [N_CHANNELS];
volatile uint32_t     synth_active   [N_CHANNELS];
volatile uint32_t     synth_state    [N_CHANNELS];
volatile uint32_t     synth_envl_add [N_CHANNELS];
volatile uint32_t     synth_envl_sub [N_CHANNELS];
volatile uint32_t     synth_envl_goal[N_CHANNELS];
volatile int_fast8_t  synth_prev     [N_CHANNELS];
volatile int_fast8_t  synth_next     [N_CHANNELS];

...

         uint8_t      CH_ACTIVE [N_NOTES];
         int_fast8_t  CH_INDEX  [N_NOTES];

volatile uint32_t     PITCH     [N_NOTES];

volatile int8_t       SAMPLE    [N_SAMPLE] = DEFAULT_SAMPLE;

如您所见,数组的大小是头文件中定义的常量:

#define N_NOTES   97               // = (9 - 1) * 12 + 1

#define N_CHANNELS 12 //EVEN NUMBER
#define N_SAMPLE   256

我希望这些数组有一个可以稍后覆盖的初始值。 对于其中一个,我可以做到:

volatile int8_t       SAMPLE    [N_SAMPLE] = DEFAULT_SAMPLE;

因为我知道 N_SAMPLE 永远是 256。 所以我可以这样定义:

#define DEFAULT_SAMPLE \
{ \
    0x7F, 0x7F, 0x7F, 0x7F, 0x7E, 0x7E, 0x7E, 0x7D, \
    0x7D, 0x7C, 0x7B, 0x7A, 0x7A, 0x79, 0x78, 0x76, \
    0x75, 0x74, 0x73, 0x71, 0x70, 0x6E, 0x6D, 0x6B, \
    0x6A, 0x68, 0x66, 0x64, 0x62, 0x60, 0x5E, 0x5C, \
    0x5A, 0x57, 0x55, 0x53, 0x50, 0x4E, 0x4B, 0x49, \
    0x46, 0x44, 0x41, 0x3E, 0x3C, 0x39, 0x36, 0x33, \
    0x30, 0x2D, 0x2A, 0x27, 0x25, 0x22, 0x1E, 0x1B, \
    0x18, 0x15, 0x12, 0x0F, 0x0C, 0x09, 0x06, 0x03, \
    0x00, 0xFC, 0xF9, 0xF6, 0xF3, 0xF0, 0xED, 0xEA, \
    0xE7, 0xE4, 0xE1, 0xDD, 0xDA, 0xD8, 0xD5, 0xD2, \
    0xCF, 0xCC, 0xC9, 0xC6, 0xC3, 0xC1, 0xBE, 0xBB, \
    0xB9, 0xB6, 0xB4, 0xB1, 0xAF, 0xAC, 0xAA, 0xA8, \
    0xA5, 0xA3, 0xA1, 0x9F, 0x9D, 0x9B, 0x99, 0x97, \
    0x95, 0x94, 0x92, 0x91, 0x8F, 0x8E, 0x8C, 0x8B, \
    0x8A, 0x89, 0x87, 0x86, 0x85, 0x85, 0x84, 0x83, \
    0x82, 0x82, 0x81, 0x81, 0x81, 0x80, 0x80, 0x80, \
    0x80, 0x80, 0x80, 0x80, 0x81, 0x81, 0x81, 0x82, \
    0x82, 0x83, 0x84, 0x85, 0x85, 0x86, 0x87, 0x89, \
    0x8A, 0x8B, 0x8C, 0x8E, 0x8F, 0x91, 0x92, 0x94, \
    0x95, 0x97, 0x99, 0x9B, 0x9D, 0x9F, 0xA1, 0xA3, \
    0xA5, 0xA8, 0xAA, 0xAC, 0xAF, 0xB1, 0xB4, 0xB6, \
    0xB9, 0xBB, 0xBE, 0xC1, 0xC3, 0xC6, 0xC9, 0xCC, \
    0xCF, 0xD2, 0xD5, 0xD8, 0xDA, 0xDD, 0xE1, 0xE4, \
    0xE7, 0xEA, 0xED, 0xF0, 0xF3, 0xF6, 0xF9, 0xFC, \
    0xFF, 0x03, 0x06, 0x09, 0x0C, 0x0F, 0x12, 0x15, \
    0x18, 0x1B, 0x1E, 0x22, 0x25, 0x27, 0x2A, 0x2D, \
    0x30, 0x33, 0x36, 0x39, 0x3C, 0x3E, 0x41, 0x44, \
    0x46, 0x49, 0x4B, 0x4E, 0x50, 0x53, 0x55, 0x57, \
    0x5A, 0x5C, 0x5E, 0x60, 0x62, 0x64, 0x66, 0x68, \
    0x6A, 0x6B, 0x6D, 0x6E, 0x70, 0x71, 0x73, 0x74, \
    0x75, 0x76, 0x78, 0x79, 0x7A, 0x7A, 0x7B, 0x7C, \
    0x7D, 0x7D, 0x7E, 0x7E, 0x7E, 0x7F, 0x7F, 0x7F  \
}

对于其他阵列我不知道这一点。 我想创建一个 #define DEFAULT_SYNTH_NEXT ,它将变成:
{ 1, 2, 3, 4, 5, 6, 7, -1}N_CHANNELS 等于 8 时,
{ 1, 2, 3, 4, 5, -1}N_CHANNELS 等于 6 时, 等等。
创建一个 #define DEFAULT_CH_ACTIVE 数组,其中包含 N_NOTES 个值为 -1 的数组。 等等等等。

是否有可能,使用 C 预处理器实现 #define 取决于先前以上述方式定义的数字? 以某种方式(递归地?)(迭代地?)产生预期的结果?

我找到了一篇文章 http://jhnet.co.uk/articles/cpp_magic 但这看起来可读性和理解性都不是很好,我还没有设法弄清楚。 我希望阅读起来更清晰。

我看到了 2 个替代方案,但它们并不理想:

备选方案 1 静态定义如下:

#define DEFAULT_SYNTH_PREV {-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
#define DEFAULT_SYNTH_NEXT {1, 2, 3, 4 ,5, 6, 7, 8, 9, 10, 11, -1}

等等,但每次更新其中一个值时我都必须手动编辑它。

备选方案 2 在 main() 函数中初始化数组,在 for() 循环中进入主循环之前。 这就是我目前正在做的,

    set_tuning(DEFAULT_TUNING);
    
    for(i=0; i<N_CHANNELS; ++i)
    {
        synth_counter[i] = 0;
        synth_pitch[i] = 0;
        synth_envelope[i] = 0;
        synth_envl_add[i] = 0;
        synth_envl_sub[i] = 0;
        synth_envl_goal[i] = 0;
        synth_active[i] = 0;
        synth_state[i] = ENVL_0;
        if (i==0)
            synth_prev[i] = -1;
        else
            synth_prev[i] = i-1;
        if (i==(N_CHANNELS - 1))
            synth_next[i] = -1;
        else
            synth_next[i] = i+1;
        synth_key[i] = -1;
    }
    for (i=0; i<N_NOTES; ++i)
    {
        CH_ACTIVE[i] = 0;
        CH_INDEX[i] = -1;
    }

这确实用所需的值预填充了数组,但与通过在默认情况下包含在项目中的启动代码中的重置处理程序中从 FLASH 复制到 RAM 来预填充预定义变量的代码相比,它不是最佳代码:

Reset_Handler:  
  ldr   r0, =_estack
  mov   sp, r0          /* set stack pointer */

/* Copy the data segment initializers from flash to SRAM */
  movs  r1, #0
  b  LoopCopyDataInit

CopyDataInit:
  ldr  r3, =_sidata
  ldr  r3, [r3, r1]
  str  r3, [r0, r1]
  adds  r1, r1, #4

LoopCopyDataInit:
  ldr  r0, =_sdata
  ldr  r3, =_edata
  adds  r2, r0, r1
  cmp  r2, r3
  bcc  CopyDataInit
  ldr  r2, =_sbss
  b  LoopFillZerobss
/* Zero fill the bss segment. */
FillZerobss:
  movs  r3, #0
  str  r3, [r2]
  adds r2, r2, #4


LoopFillZerobss:
  ldr  r3, = _ebss
  cmp  r2, r3
  bcc  FillZerobss

/* Call the clock system intitialization function.*/
  bl  SystemInit
/* Call static constructors */
    bl __libc_init_array
/* Call the application's entry point.*/
  bl  main

这就是为什么我仍然希望能够“动态地”#define 数组初始化程序

对于你的第一个要求,你可以简单地做这样的事情:

#if N_CHANNELS == 8
    #define DEFAULT_SYNTH_NEXT { 1, 2, 3, 4, 5, 6, 7, -1 }
#endif

#if N_CHANNELS == 6
    #define DEFAULT_SYNTH_NEXT { 1, 2, 3, 4, 5, -1 }
#endif

等等。

不知道第二个怎么做。


或者,根据@chux 的评论,您可以这样做:

#if N_CHANNELS == 8
    #define DEFAULT_SYNTH_NEXT { 1, 2, 3, 4, 5, 6, 7, -1 }
#elif N_CHANNELS == 6
    #define DEFAULT_SYNTH_NEXT { 1, 2, 3, 4, 5, -1 }
...
#else
    #error "Unsupported N_CHANNELS value"
#endif

这真的很难看,但你可以使用指定的初始化器:

#include <assert.h>
#include <stdio.h>

#define VAL(max, x) [(x <= max) ? (x - 1) : 0] = (x <= max) ? x : 1,

#define MAKE_ARRAY(name, max)           \
static_assert((max > 0) && (max < 9));  \
volatile int name[] = {                 \
    VAL(max, 1)                         \
    VAL(max, 2)                         \
    VAL(max, 3)                         \
    VAL(max, 4)                         \
    VAL(max, 5)                         \
    VAL(max, 6)                         \
    VAL(max, 7)                         \
    VAL(max, 8)                         \
    [max - 1] = -1                      \
}

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Woverride-init"

MAKE_ARRAY(synth, 3);
// More arrays here

#pragma GCC diagnostic pop

int main(void)
{
    size_t size = sizeof(synth) / sizeof(*synth);

    printf("array size = %zu\nvalues = {", size);
    for (size_t i = 0; i < size; i++)
    {
        printf(" %d,", synth[i]);
    }
    printf(" }\n");
    return 0;
}

MAKE_ARRAY(synth, 3) 扩展为:

static_assert((3 > 0) && (3 < 9));
volatile int synth[] = {[0]=1,[1]=2,[2]=-1,[0]=1,[0]=1,[0]=1,[0]=1,[0]=1};

输出是:

array size = 3
values = { 1, 2, -1, }

我正在使用

#pragma GCC diagnostic ignored "-Woverride-init"

由于以下重复项而使编译器静音:,[0]=1,[0]=1,[0]=1 ...

否则我得到:

warning: initialized field overwritten [-Woverride-init]

您还可以使用:

#include <stdio.h>
#include <assert.h>

#define VAL1 -1
#define VAL2 1
#define VAL3 VAL2, 2
#define VAL4 VAL3, 3
#define VAL5 VAL4, 4
#define VAL6 VAL5, 5
#define VAL7 VAL6, 6
#define VAL8 VAL7, 7

#define MAKE_ARRAY(name, max)           \
static_assert((max > 0) && (max < 9));  \
volatile int name[] = {VAL##max, -1}

MAKE_ARRAY(synth, 3);
// More arrays here

int main(void)
{
    size_t size = sizeof(synth) / sizeof(*synth);

    printf("array size = %zu\nvalues = {", size);
    for (size_t i = 0; i < size; i++)
    {
        printf(" %d,", synth[i]);
    }
    printf(" }\n");
    return 0;
}

在这种情况下 MAKE_ARRAY(synth, 3) 扩展为:

static_assert((3 > 0) && (3 < 9));
volatile int synth[] = {1, 2, -1};

但是你不能使用:

#define NELEMS 3
MAKE_ARRAY(synth, NELEMS);

也不

enum {NELEMS = 3};
MAKE_ARRAY(synth, NELEMS);

虽然第一个版本允许您这样做。

I found an article http://jhnet.co.uk/articles/cpp_magic but this does not look very readable and understandable and I didn't manage to figure it out yet.

无需弄清楚魔法是如何工作的,您可以使用为您完成魔法的库。

使用升压预处理器:

#include <boost/preprocessor/arithmetic/dec.hpp>
#include <boost/preprocessor/repetition/enum_shifted.hpp>

#define IOTA_FROM_0(Z_, COUNT_, _) \
    BOOST_PP_DEC(COUNT_)
#define DEFAULT_SYNTH_PREV \
    { -1, BOOST_PP_ENUM_SHIFTED(N_CHANNELS, IOTA_FROM_0, ) }

#define IOTA(Z_, COUNT_, _) COUNT_
#define DEFAULT_SYNTH_NEXT \
    { BOOST_PP_ENUM_SHIFTED(N_CHANNELS, IOTA, ) , -1 }

...是的,boost 预处理器在 C 中工作。

这就用到了BOOST_PP_ENUM_SHIFTED, which is almost everything you want. For your PREV macro, this just adds BOOST_PP_DEC。由于在 0 处饱和,我们手动插入 -1(与您在循环中所做的没什么不同,尽管对于循环情况 0-1 等于 -1!)

演示

coliru