0

For efficient code maintenance I need to make sure that the value at index 0 of an array is a specific predefined value. The following code doesn't work:

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

It gives the following compilation error:

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

The addr is defined as const uint16_t, so I was thinking that its value is known at the compilation time.

Is there an efficient way to perform such check at the compilation time?

Clarification: I understand that in this way I can't use the _Static_assert, const doesn't make a variable's value known at the compilation time. What I am asking is if anyone knows any kind of trick to deal with such issues.

The satisfying solution was proposed by Kamil Cuk. The initialization can be done with specifying the index:

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

In such case if there would be additional initialization of entry at index [SPECIFIC_ADDR_IDX], the compiler will issue a warning (not guaranteed but most compilers will). Just make sure to compile with warning=error option ON.

Alex Lop.
  • 6,810
  • 1
  • 26
  • 45
  • `globArr` is not a constant expression. Variable values are not constant expressions. – KamilCuk Dec 12 '18 at 08:38
  • Why not at runtime (in debug version)? And why you need initialization with zero when it is the default value? – i486 Dec 12 '18 at 08:45
  • 1
    Your static_assert shall check if your first initialization line added the `SPECIFIC_ADDR` to the member `.addr`. Your code has 6 lines above the check the correct value assigned. What do you want to assert? If somebody changes the initialization list, she can change the static assert too. – harper Dec 12 '18 at 08:50
  • @i486 This is just an example. You can ignore the initialization with 0. – Alex Lop. Dec 12 '18 at 08:51
  • @harper Right. But if someone adds entries to the `globArr` and then fails on compilation, it will indicate that the static assert was not added just for fun but there is a reason and probably, the most logical way would be either to dig and understand why it was added or just keep the SPECIFIC_ADDR at SPECIFIC_ADDR_IDX. Also note that this is just an example. In the real project the array is much bigger, not just 6 lines as you see here. – Alex Lop. Dec 12 '18 at 08:54
  • You could add comments next to relevant lines explaining the restrictions. It's not as good as assertion, but will raise warning flags to someone who's not a completely incompetent. – user694733 Dec 12 '18 at 09:08
  • @user694733 Thanks! That's what I have today, just a comment. The problem with comments is that they has to be maintained too and once there many comments, people just tend to ignore them. That's why I am looking for something *stronger* than just a comment. – Alex Lop. Dec 12 '18 at 09:10

1 Answers1

1

You can just specify the intex in initialization:

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

Anyway you need to do runtime assertion, so use assert:

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

static_assert needs a constant expression. You can't write static assertions on variables values. Even if you did static const struct_t globArr[] still globArr value is not a constant expression. The C language has no constexpr (or consteval) specifier like C++. So, sadly, you can't do that in C.

const is just a modifier, it says that the variable can't be modified through this handle. const variables can be modified and are not immutable in C.

It's just like you can't do:

#if globArr[SPECIFIC_ADDR_IDX].addr == SPECIFIC_ADDR

the same you can't

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

What is a constant expression is probably nicely enumerated at cppreference. The result of array subscript [] and member access . operators is not a constant expression, therefore it can't be used in static assertion.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • 1
    Thanks for your answer. Checking it at runtime is the obvious thing but I don't want to perform this check all the time when I pass through a specific code due to performance issues and also there is no reason to check it all the time. Also the testing of that specific flow might take some time and when the assert raises it might be few days after the code is delivered (not everything is checked in pre checkin criteria). So the best thing would be to detect such thing at the compilation time. The question is if there is an existing trick for such thing... – Alex Lop. Dec 12 '18 at 09:02
  • ... anyway, I think what you suggested `[SPECIFIC_ADDR_IDX] = { .addr = SPECIFIC_ADDR, .val = 0 },` may do the job. If anyone will try to initialize elements at SPECIFIC_ADDR_IDX, it will raise compilation error. – Alex Lop. Dec 12 '18 at 09:19
  • No. It's just a warning on gcc, see [godbolt](https://godbolt.org/z/CC6vXZ). I think I remember the C standard allowing duplicate initialization. – KamilCuk Dec 12 '18 at 09:23