【问题标题】:"variable" length initialisation using C preprocessor使用 C 预处理器进行“可变”长度初始化
【发布时间】:2021-08-10 20:03:21
【问题描述】:

我正在为 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 是在进入主循环之前在 for() 循环中初始化 main() 函数中的数组。 这就是我目前正在做的,

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

这确实用所需的值预填充数组,但它不如通过从闪存复制到默认包含在项目中的启动代码中的重置处理程序中的 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 数组初始化器

【问题讨论】:

  • 忘记预处理器。编写代码生成器。
  • #define N_NOTES 97 // = (9 - 1) * 12 + 1 - 不要这样做。如果一个数字由于数学运算而有意义,请将数学运算放入数字定义中。我看到了一些错误,这些错误会带来不愉快的后果,比如出现这样的数学错误。
  • 按要求回答问题 - 可能是可能的,但这将是一个看起来非常糟糕的代码。复杂的 PP 构造令人不快,是排除故障和产生神秘错误的噩梦。只是代码生成。
  • @Neil - 静态变量在超出范围时保留值。但这些都是全局变量,所以总是在范围内。全局,因为中断处理函数必须看到它们,它们代表设备的全局状态。预处理器宏只定义初始值。
  • @SergeyA "just codegen" - 我有点希望在这个项目中避免这种情况,因为它似乎不够复杂,但毕竟这可能是解决方案。

标签: c c-preprocessor


【解决方案1】:

对于您的第一个要求,您可以简单地执行以下操作:

#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

【讨论】:

  • 这可能是我特定情况下的解决方案,因为N_CHANNELS 的可能值是有限的。正如我之前的评论中提到的,可能的值为 2、4、6、8、10、12、14、16。对于我的所有其他数组,我可以使用“指定初始化器”gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Designated-Inits.htmvolatile uint32_t synth_state [N_CHANNELS] = {[0 ... (N_CHANNELS-1)] = ENVL_0};
  • #if ... #elif .... #elif .... #else .... #endif 允许在#else 中使用#error "useful message"
  • @chux-ReinstateMonica 好主意,谢谢。
  • N_CHANNELS 有限,这可能是最简单且最不容易出错的解决方案。
【解决方案2】:

真的很丑,但是你可以使用指定的初始化器:

#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);

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

【讨论】:

    【解决方案3】:
    我找到了一篇文章 http://jhnet.co.uk/articles/cpp_magic 但这看起来不太可读和可理解,我还没有设法弄清楚。

    与其解开魔法的工作原理,您可以使用为您完成魔法的库。

    使用 boost 预处理器:

    #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,这几乎是你想要的一切。对于您的 PREV 宏,这只是添加了BOOST_PP_DEC。由于在0 处饱和,我们手动插入-1(与您在循环中所做的没有太大不同,尽管对于循环情况0-1 确实等于-1!)

    演示

    开启coliru

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-10-09
      • 2016-11-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-06-24
      • 2012-06-25
      • 2017-10-05
      相关资源
      最近更新 更多