首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >在C99中具有可能溢出的常量表达式的初始化器

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

Stack Overflow用户
提问于 2015-04-15 13:59:51
回答 3查看 778关注 0票数 6

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

代码语言:javascript
运行
复制
int a;
unsigned long b[] = {(unsigned long)&a+1};

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

  1. 整数常量表达式应具有整数类型,并且只能具有整数常量(.)的操作数。整数常量表达式中的强制转换运算符只能将算术类型转换为整数类型,但作为操作数的一部分,则应转换为相当大的运算符。
  2. 对于初始化器中的常量表达式,允许有更大的空间。这种常量的表述应是或评估以下之一:
代码语言:javascript
运行
复制
- an arithmetic constant expression,
- (...)
- an address constant for an object type plus or minus an integer constant expression.

但是,由于存在加法溢出的可能性,这可能不是一个常量表达式,因此不能使用有效的C99代码。

有人能确认一下我的推理是否正确吗?

请注意,GCC和Clang都不带警告地接受此代码,即使在使用-std=c99 -pedantic时也是如此。但是,当转换为unsigned int而不是unsigned long时,即使用以下代码:

代码语言:javascript
运行
复制
int a;
unsigned long b[] = {(unsigned int)&a+1};

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

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2015-04-15 14:20:11

从这个clang开发人员线程中可以看到类似的问题:函数指针在转换为长时是编译时间常数,而不是int?,其基本原理是,标准不要求编译器支持这个(这个场景不包括在6.6p7中的任何一个符号中),尽管允许它支持这个支持截断的地址将是很麻烦的:

--我假设在目标上,sizeof(int) < sizeof(void(*)()) == sizeof(long)。问题是工具链几乎肯定不能将截断的地址表示为重新定位。 C只需要实现支持初始化值,这些值要么是(1)常量二进制数据,(2)某个对象的地址,要么是(3)或添加到某个对象地址的偏移量。我们允许(但不需要)支持更复杂的事情,比如减去两个地址或将一个地址乘以一个常量,或者,就像在您的例子中那样,截断地址的顶部位。这种计算需要从汇编程序到加载程序的整个工具链的支持,包括各种文件格式。这种支持一般不存在。

您的情况(它正在转换指向整数类型的指针)不适合6.6段落7下的任何情况。

对于初始化器中的常量表达式,允许有更大的空间。这种常量的表述应是或评估以下之一:

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

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

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

clanggcc都不接受这一点。

票数 2
EN

Stack Overflow用户

发布于 2015-04-15 15:05:06

符合规范的实现不需要接受此代码。你在你的问题中引用了有关的段落:

  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的。

票数 2
EN

Stack Overflow用户

发布于 2015-04-15 14:23:31

首先,初始化器不一定是一个常量表达式。如果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版本中有一个编译错误。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/29652323

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档