"error: initializer element is not constant" when using bitshift but not add or subtract, in C

"error: initializer element is not constant" when using bitshift but not add or subtract, in C

我的嵌入式系统有两个内存区域。我创建了宏来在这些区域之间切换。我希望能够在编译时执行这些宏,但我得到的是某些操作的 error: initializer element is not constant 而不是其他操作。我把这个例子归结为这个。这些是全局变量:

__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

看来我不能做除法或位移,但是加法或减法是可以的。

我最初 运行 尝试减去一个固定偏移量,然后除以 4 时进入这个。也许有更简单的方法来做到这一点?我正在使用 (GCC) 7.2.0

严格来说,标准C中禁止上述所有形式的初始化。

允许使用以下类型的表达式初始化具有静态存储的对象(在 here 中有更详细的描述,并带有标准引用):

  1. 一个算术常量表达式
  2. NULL指针常量;
  3. 一个地址常量表达式;
  4. 一些完整对象类型的地址常量表达式,加上或减去整数常量表达式。

具体来说,一个算术常量表达式可以由算术运算符、sizeof运算符和算术类型的字面操作数组成。 buf0 是一个变量,而不是文字,因此示例中表达式的 none 符合算术常量表达式的条件。表达式类型 2、3 和 4 也不适用,因此编译器可以自由拒绝所有使用 buf0.

的初始化形式

这是有道理的,因为 buf0 的地址只在 link 时解析,而不是在编译时解析,所以它不能用来组成编译时常量表达式。

但是,当目标的地址宽度和目标 int 宽度相同时,gcc(和其他 C 编译器,包括 clang 和 icc)将允许后两种形式,这是一种扩展。例如,在 x86-64 上,我们会得到:

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

并且,如果我们检查生成的程序集 (godbolt),我们可以看到 works_1works_2:

的 gcc 扩展代码
works_1:
  .quad buf0+2
works_2:
  .quad buf0+16

GNU 汇编程序允许使用 +- 在静态地址计算中进行简单算术运算,但不允许更复杂的表达式。理论上,如果汇编程序(和 linker)允许更高级的地址算法,如移位,C 编译器也可以允许这种扩展(但不严格符合)。

就纯 C 语言而言,您不应该能够在算术常量表达式中使用依赖于地址的值。您可以在整数的初始值设定项中使用 (unsigned int) buf0 这一事实是特定于编译器的扩展。

在此扩展中,对 address 常量施加的限制通常仍然适用。这些限制植根于现实生活中 loader 的能力。在一般情况下,您的 buf0 的具体地址并不是真正的编译时常量。它的实际值只会在加载时知道。加载程序将不得不执行依赖于此地址的 "constant expressions" 的最后更新。而loader的运算能力相当有限。装载机知道如何加减,但仅此而已。出于这个原因,您可以在地址常量表达式中使用加法和减法(以及最终归结为地址加法或减法的其他运算符),但不能使用其他任何东西。装载机不能轮班。

此外,您的编译器甚至在其中一些初始化程序中接受 (unsigned int) buf0 纯属巧合。显然,在您的平台上,指针大小与 unsigned int 的大小相同。如果不是这种情况,从指针到 unsigned int 的转换将不得不截断或扩展该值。加载程序也不能这样做,这意味着如果不是巧合,你所有的声明都将无法编译。这就是为什么当你想将指针转换为整数时,更好的主意是使用 uintptr_t 而不是 unsigned int.