地址常量表达式

C 语言中具有静态生存周期的变量的初始化器必须是常量表达式。当初始化器不是常量表达式时,gcc 和 clang 的报错信息不尽相同。

gcc 的报错信息是 error: initializer element is not constant clang 的报错信息是 error: initializer element is not a compile-time constant

clang 用的是“编译时常量”(compile-time constant),实际上 C99 标准里没有“编译时常量”这个概念,只有“常量表达式”。

A constant expression can be evaluated during translation rather than runtime, and accordingly may be used in any place that a constant may be.

C99 规定了初始化器中可用的常量表达式:

More latitude is permitted for constant expressions in initializers. Such a constant expression shall be, or evaluate to, one of the following:

  • — an arithmetic constant expression,
  • — a null pointer constant,
  • — an address constant, or
  • — an address constant for an object type plus or minus an integer constant expression.

An address constant is a null pointer, a pointer to an lvalue designating an object of static storage duration, or a pointer to a function designator; it shall be created explicitly using the unary & operator or an integer constant cast to pointer type, or implicitly by the use of an expression of array or function type. The array-subscript [] and member-access . and -> operators, the address & and indirection * unary operators, and pointer casts may be used in the creation of an address constant, but the value of an object shall not be accessed by use of these operators.

也就是说,你可以在初始化器中使用变量、变量的成员和函数的地址,也可以在地址的基础上加上个偏移量。

“address constant” 不能算作 “compile-time constant”。 尽管编译器可能在生成 ELF 文件之时,就决定了变量的虚拟地址,但在作为共享库 (shared library) 被加载时或其他情况下,这些变量的虚拟地址最终还需要加载器 (loader) 的调整。对于这些变量地址的引用,需要加载器的重定位功能。

c99 标准对于 “address constant” 的运算只规定了可以加减整数常量表达式。

1
2
3
#include <stdint.h>
#include <stdlib.h>
uintptr_t addr = (uintptr_t)malloc + (uintptr_t)free + 0x2345;

gcc/clang 都不支持上面这种用法。其实上面这种情况可以通过对 &addr 这个位置进行累积的重定位做到 (Elf_Rel/Elf64_Rel 的 r_addend 存储在 &addr 这个位置)。

1
2
3
4
// 64bit machine
#include <stdint.h>
#include <stdlib.h>
uintptr_t addr = (uint32_t)malloc;

这种情况,gcc 给出的错误信息是 error: initializer element is not computable at load time

右值(地址, 64 位)需要在重定位后强制转换为 32 位整数,再赋值给左值( 64 位)。要支持这样情况的话,可能需要 ELF 的重定位信息支持。

编译器的版本: clang version 3.3 gcc version 4.8.1