6

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

int a;
unsigned long b[] = {(unsigned long)&a+1};

根据我对 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};

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

4

3 回答 3

2

此代码不需要被符合要求的实现所接受。您在问题中引用了相关段落:

  1. 初始化器中的常量表达式允许更大的自由度。这样的常量表达式应为或评估为以下之一:
    • 算术常数表达式,
    • 一个空指针常量,
    • 地址常量,或
    • 对象类型的地址常量加上或减去整数常量表达式。

(unsigned long)&x这些都不是。由于 C11 6.6/8,它不是算术常数:

算术常量表达式中的强制转换运算符只能将算术类型转换为算术类型

(指针类型不是算术类型,6.2.5/18);它不是地址常量,因为所有地址常量都是指针(6.6/9)。最后一个指针加上或减去一个 ICE 是另一个指针,所以它也不是。


但是 6.6/10 表示实现可以接受其他形式的常量表达式。我不确定这是否意味着原始代码应该被称为格式错误(格式错误的代码需要诊断)。显然,您的编译器在这里接受其他一些常量表达式。


下一个问题是从指针到整数的转换是实现定义的。如果没有对应于特定指针的整数表示,它也可能是未定义的。(6.3.2.3/6)

最后,最后+ 1没有区别。unsigned long算术在加法和减法上是明确定义的,所以当且仅当(unsigned long)&x是 OK 时它是 OK 的。

于 2015-04-15T15:05:06.717 回答
2

来自这个clang开发人员线程的类似问题:Function pointer is compile-time constant when cast to long but not int? 理由是该标准不要求编译器支持这一点(这种情况不包含在 中的任何项目符号中6.6p7),尽管允许支持这种支持截断地址会很麻烦:

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

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

您的案例将指向整数类型的指针转​​换为不适合6.6段落下的任何案例7

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

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

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

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

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

于 2015-04-15T14:20:11.510 回答
1

首先,您的初始化程序不一定是常量表达式。如果a具有本地范围,则在将其推入堆栈时,会在运行时为其分配一个地址。C11 6.6/7 说,为了使指针成为常量表达式,它必须是地址常量,在 6.6/9 中定义为:

地址常量是空指针、指向指定静态存储持续时间对象的左值的指针或指向函数指示符的指针;它应使用一元 & 运算符或转换为指针类型的整数常量显式创建,或通过使用数组或函数类型的表达式隐式创建。

(强调我的)


至于你的代码是否是标准C,是的。指针转换为整数是允许的,尽管它们可能带有各种形式的不良指定行为。在 6.5/6 中指定:

任何指针类型都可以转换为整数类型。除非前面指定,结果是实现定义的。如果结果不能以整数类型表示,则行为未定义。结果不必在任何整数类型的值范围内。

为了安全地确保指针可以放入整数,您需要使用uintptr_t. 但我不认为指向整数转换的指针是你发布这个问题的原因。


关于整数溢出是否会阻止它成为编译时间常数,我不确定你从哪里得到这个想法。我不相信你的推理是正确的,例如(INT_MAX + INT_MAX)是一个编译时间常数,它也保证会溢出。(GCC 给你一个警告。)如果它溢出,它将调用未定义的行为。


至于为什么你会得到关于表达式不是编译时常量的错误,我不知道。我无法在 gcc 4.9.1 上重现它。我尝试a同时声明静态和自动存储持续时间,但没有区别。

听起来您不知何故意外编译为 C90,在这种情况下 gcc 会告诉您“错误:初始化元素在加载时不可计算”。或者也许有一个编译器错误已在我的 gcc 版本中修复。

于 2015-04-15T14:23:31.067 回答