My embedded system has two regions of memory. I created macros to switch between these regions. I would like to be able to execute these macros at compile time but I'm getting error: initializer element is not constant
for certain operations and not others. I've boiled down the example to this. These are global variables:
__attribute__((section(".vmem_constant.data"))) unsigned int buf0[1024];
unsigned int buf_word_ptr = ((unsigned int)buf0)>>2; // doesn't work
unsigned int buf_word_ptr2 = ((unsigned int)buf0)/4; // doesn't work
unsigned int buf_word_ptr3 = ((((unsigned int)x)-0x40000)>>2); // original problem doesn't work
unsigned int works_1 = ((unsigned int)buf0) + 2; // works
unsigned int works_2 = buf0 + 16; // works
It seems like I can't do a divide or bitshift, however add or subtract is ok.
I originally ran into this when I was trying to subtract a fixed offset, and then divide by 4. Maybe there is an easier way to do this? I'm using (GCC) 7.2.0
Strictly speaking, all above forms of initialization are forbidden in standard C.
Objects with static storage are allowed to be initialized with the following kinds of expressions (described in more detail here, with standard references):
Specifically, an arithmetic constant expression may be made up of arithmetic operators, the sizeof operator, and literal operands of arithmetic types. buf0
is a variable, not a literal, and therefore none of the expressions in the example qualifies as an arithmetic constant expression. Expression kinds 2, 3 and 4 are not applicable as well, so the compiler is free to reject all initialization forms that use buf0
.
This makes sense since, address of buf0
is only resolved at link time, not at compile time, so it cannot be used to make up compile time constant expressions.
However, gcc (and other C compilers, including clang and icc) will allow the two latter forms, when the target's address width and destination int width are the same, which is an extension. For example, on x86-64, we'd get:
uint64_t fails = ((uint32_t)buf0) + 2; // fails
uint64_t works_1 = ((uint64_t)buf0) + 2; // works
uint64_t works_2 = (uint64_t)buf0 + 16ul; // works
And, if we inspect the generated assembly (godbolt), we can see the gcc expanded code for works_1
and works_2
:
works_1:
.quad buf0+2
works_2:
.quad buf0+16
The GNU assembler allows simple arithmetics in static address calculations, using +
and -
, but will not allow for more complex expressions. In theory, if the assembler (and linker) allow for more advanced address arithmetics, like shifting, the C compiler could allow for this extension as well (but not being strictly conforming).