【问题标题】:Checking constant variable's value at the compilation time在编译时检查常量变量的值
【发布时间】:2018-12-12 08:20:49
【问题描述】:

为了有效地维护代码,我需要确保数组索引 0 处的值是特定的预定义值。以下代码不起作用:

#define SPECIFIC_ADDR_IDX 0
#define SPECIFIC_ADDR     8
#define NOT_SPECIFIC_ADDR1 12
#define NOT_SPECIFIC_ADDR2 16

typedef struct _struct_s
{
    const uint16_t addr; // addresses are constant and are not mutable
    uint32_t       val;
} struct_s;

struct_s globArr[] =
{
    {.addr = SPECIFIC_ADDR,      .val = 0},
    {.addr = NOT_SPECIFIC_ADDR1, .val = 0},
    {.addr = NOT_SPECIFIC_ADDR2, .val = 0},
};

// make sure the address at the SPECIFIC_ADDR_IDX is SPECIFIC_ADDR
_Static_assert(globArr[SPECIFIC_ADDR_IDX].addr == SPECIFIC_ADDR, " Illegal!");

它给出以下编译错误:

error: expression in static assertion is not constant
 _Static_assert (globArr[SPECIFIC_ADDR_IDX].addr == SPECIFIC_ADDR, " Illegal!");
                 ~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~

addr 被定义为const uint16_t,所以我认为它的值在编译时是已知的。

有没有一种在编译时执行这种检查的有效方法?

澄清:我知道这样我不能使用_Static_assertconst 不会在编译时知道变量的值。我要问的是是否有人知道处理此类问题的任何技巧

令人满意的解决方案由 Kamil Cuk 提出。初始化可以通过指定索引来完成:

struct_t globArr[] =
{
    [SPECIFIC_ADDR_IDX] = { .addr = SPECIFIC_ADDR, .val = 0 },
    {.addr = NOT_SPECIFIC_ADDR1, .val = 0},
    {.addr = NOT_SPECIFIC_ADDR2, .val = 0},
};

在这种情况下,如果在索引 [SPECIFIC_ADDR_IDX] 处有额外的条目初始化,编译器将发出警告(不保证,但大多数编译器会)。只需确保在 warning=error 选项打开的情况下进行编译。

【问题讨论】:

  • globArr 不是常量表达式。变量值不是常量表达式。
  • 为什么不在运行时(在调试版本中)?当它是默认值时,为什么你需要用零初始化?
  • 您的 static_assert 将检查您的第一个初始化行是否将 SPECIFIC_ADDR 添加到成员 .addr。您的代码在检查分配的正确值上方有 6 行。你想断言什么?如果有人更改了初始化列表,她也可以更改静态断言。
  • @i486 这只是一个例子。你可以用 0 忽略初始化。
  • @harper 对。但是,如果有人向globArr 添加条目,然后编译失败,这将表明添加静态断言不是为了好玩,而是有一个原因,并且可能最合乎逻辑的方法是挖掘并理解它的原因已添加或仅将 SPECIFIC_ADDR 保留为 SPECIFIC_ADDR_IDX。另请注意,这只是一个示例。在实际项目中,数组要大得多,而不仅仅是您在此处看到的 6 行。

标签: c constants static-assert


【解决方案1】:

您可以在初始化时指定 intex:

#define SPECIFIC_ADDR_IDX 0
#define SPECIFIC_ADDR     8
#define NOT_SPECIFIC_ADDR1 12
#define NOT_SPECIFIC_ADDR2 16

typedef struct _struct_s
{
    const uint16_t addr; // addresses are constant and are not mutable
    uint32_t       val;
} struct_t;

struct_t globArr[] =
{
    [SPECIFIC_ADDR_IDX] = { .addr = SPECIFIC_ADDR, .val = 0 },
    {.addr = NOT_SPECIFIC_ADDR1, .val = 0},
    {.addr = NOT_SPECIFIC_ADDR2, .val = 0},
};

反正你需要做运行时断言,所以使用断言:

void globArr_unittest(void) {
     assert(globArr[SPECIFIC_ADDR_IDX].addr == SPECIFIC_ADDR);
}

static_assert 需要一个常量表达式。您不能在变量值上编写静态断言。即使你做了static const struct_t globArr[] 仍然globArr value 不是一个常量表达式。 C 语言没有像 C++ 这样的 constexpr(或 consteval)说明符。所以,遗憾的是,你不能在 C 中做到这一点。

const 只是一个修饰符,它表示该变量不能通过这个句柄进行修改。 const 变量可以修改,在 C 中不是不可变的。

这就像你做不到:

#if globArr[SPECIFIC_ADDR_IDX].addr == SPECIFIC_ADDR

你做不到的

static_assert(globArr[SPECIFIC_ADDR_IDX].addr == SPECIFIC_ADDR, "");

什么是常量表达式可能很好地列举在cppreference。数组下标[]和成员访问.运算符的结果不是常量表达式,因此不能用于静态断言。

【讨论】:

  • 感谢您的回答。在运行时检查它是显而易见的事情,但由于性能问题,我不想在通过特定代码时一直执行此检查,也没有理由一直检查它。此外,该特定流程的测试可能需要一些时间,并且当断言提出时,可能是在代码交付后几天(并非所有内容都在预签入标准中进行检查)。所以最好的办法是在编译时检测到这样的事情。问题是这样的事情是否存在现有技巧......
  • ...无论如何,我认为您建议的[SPECIFIC_ADDR_IDX] = { .addr = SPECIFIC_ADDR, .val = 0 }, 可能会完成这项工作。如果有人尝试在 SPECIFIC_ADDR_IDX 处初始化元素,则会引发编译错误。
  • 没有。这只是对 gcc 的警告,请参阅godbolt。我想我记得允许重复初始化的 C 标准。
猜你喜欢
  • 2014-09-03
  • 2012-08-07
  • 2012-02-23
  • 2011-03-19
  • 2019-06-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多