具有常量表达式的初始化程序可能在C99中溢出

ano*_*nol 6 c c99 undefined-behavior language-lawyer constant-expression

这是有效的C99代码吗?如果是这样,它是否定义了实现定义的行为?

int a;
unsigned long b[] = {(unsigned long)&a+1};
Run Code Online (Sandbox Code Playgroud)

根据我对C99标准的理解,从ISO C99标准中的§6.6,这可能是有效的:

  1. 整数常量表达式应具有整数类型,并且只能具有整数常量的操作数(...)整数常量表达式中的转换运算符只能将算术类型转换为整数类型,除非作为sizeof运算符的操作数的一部分.

  2. 初始化器中的常量表达式允许更大的纬度.这样的常量表达式应为或评估为以下之一:

    • 算术常量表达式,
    • (......)
    • 对象类型的地址常量加上或减去整数常量表达式.

但是,由于存在添加溢出的可能性,因此可能不会将其视为常量表达式,因此无效C99代码.

有人可以确认我的推理是否正确吗?

请注意,即使使用,GCC和Clang也会在没有警告的情况下接受此代码-std=c99 -pedantic.但是,在转换时,unsigned int而不是unsigned long使用以下代码:

int a;
unsigned long b[] = {(unsigned int)&a+1};
Run Code Online (Sandbox Code Playgroud)

然后两个编译器都抱怨表达式不是编译时常量.

Sha*_*our 2

从这个 clang 开发人员线程中讨论了一个类似的问题:函数指针在转换为 long 但不是 int 时是编译时常量?理由是该标准不要求编译器支持这种情况(这种情况不包含在 中的任何项目符号中6.6p7),尽管允许支持这种情况,但支持截断的地址将是繁重的:

我假设你的 target 上的 sizeof(int) < sizeof(void(*)()) == sizeof(long)。问题是工具链几乎肯定无法将截断的地址表示为重定位。

C 仅要求实现支持初始值设定项值,这些值可以是 (1) 常量二进制数据,(2) 某个对象的地址,或者 (3) 或添加到某个对象地址的偏移量。我们被允许(但不是必需)支持更深奥的事情,例如减去两个地址或将地址乘以常数,或者如您的情况,截断地址的最高位。 这种计算需要从汇编器到加载器的整个工具链的支持,包括一路上的各种文件格式。这种支持通常不存在。

您将指针强制转换为整数类型的情况不适合6.6paragraph下的任何情况7

初始值设定项中的常量表达式允许有更多的自由度。这样的常量表达式应为以下之一或计算结果为以下之一:

  • 算术常量表达式,
  • 空指针常量,
  • 地址常量,或
  • 对象类型的地址常量加上或减去整型常量表达式。

但正如后置编译器中提到的,允许支持其他形式的常量表达式:

实现可以接受其他形式的常量表达式。

但既不clang也不gcc接受这一点。