将局部变量的地址作为C++11中的常量表达式

Is taking the address of a local variable a constant expression in C++11?

本文关键字:常量 C++11 表达式 局部变量 地址      更新时间:2023-10-16

以下C++11程序:

int x = 42;
void f()
{
        int y = 43;
        static_assert(&x < &y, "foo");
}
int main()
{
        f();
}

没有用gcc 4.7编译,因为它抱怨道:

error: ‘&y’ is not a constant expression

这符合我的直觉。y的地址可能会随着每次调用f而发生变化,因此在转换过程中当然无法计算。

然而,5.19[expr.const]中的所有要点似乎都没有排除它是一个常量表达式。

我看到的仅有的两个竞争者是:

左值到右值的转换。。。

但除非我弄错了(?),否则程序中没有左值到右值的转换。

引用变量[sipp]的id-expression,除非:

  • 它是用常量表达式初始化的

CCD_ 4是什么-它用常数表达式CCD_。

那么,这是标准中的错误,还是我遗漏了什么?

更新:

这非常令人困惑,但我认为我已经掌握了这一点,所以让我举一个例子来展示正在发生的事情:

int x = 42;
void f()
{
        int y = 43;
        // address constant expressions:    
        constexpr int* px = &x; // OK
        constexpr int* py = &y; // ERROR: pointer context for local variable
        // boolean constant expressions:
        constexpr bool bx = &x; // OK
        constexpr bool by = &y; // OK
        // comparison constant expressions:
        constexpr bool eq = (&x == &y); // OK
        constexpr bool lt = (&x < &y); // ERROR: undefined behaviour disqualifies 
                                                 a constant expression
}
int main()
{
        f();
}

首先区分核心常量表达式(5.19p2)和常量表达式(5.1 9p4)。常量表达式的特定子表达式只能是核心常量表达式,而不是常量表达式。也就是说,作为一个常量表达式是全表达式的一个属性,而不是子表达式。它进一步要求查看使用完整表达式的上下文。

因此,事实证明,gcc错误具有误导性。首先,CCD_ 6在某些上下文中可以是常数表达式。其次,&x < &y不是常量表达式的原因是不相关指针的比较,而不是子表达式&y的比较。

让我们尝试使用n3485来确定静态断言中的表达式必须逐步满足哪些要求。

[dcl.dcl]/1

静态资产声明:
static_assert (常量表达式,字符串文字) ;atr

[dcl.dcl]/4

静态资产声明中,常量表达式应为可在上下文中转换为bool的常量表达式。

[expr.const]/4

文字常量表达式引用常量表达式地址常量表达式统称为常量表达式


那么&x < &y是什么类型的常量表达式呢?它是而不是一个地址常量表达式

[expr.const]/4

地址常量表达式是类型为std::nullptr_t或指针类型为[…]的prvalue核心常量表达式(根据上下文进行转换后)。

根据[expr.rel]/1,&x < &y的类型为bool

它也不是引用常量表达式,因此它必须是文字常量表达式(如果有)

文字常量表达式是文字类型〔…〕的prvalue核心常量表达式

因此,&x < &y必须满足核心常数表达式的要求。


正如TemplateRex和hvd在评论中指出的那样,在这种特殊情况下,&x < &y不满足核心常数表达式的要求:

[expr.const]/2

[核心常量表达式不得包含]关系运算符或等式运算符,其中未指定结果;

[expr.rel]/2

如果相同类型的两个指针pq指向不是同一对象或同一数组的元素的不同对象或指向不同函数,或者如果它们中只有一个为空,则p<qp>qp<=qp>=q的结果是未指定的。

然而,对于这样的例子

int arr[2] = {1, 2};
static_assert(&a[0] < &a[1], "");

表达式CCD_ 25也满足这一要求。

是的,您忽略了这样一个事实,即虽然y本身是用常量表达式初始化的,但这与&y不同。

y的地址可能会有很大的变化,这取决于您的调用堆栈历史记录。

C++11 5.19 Constant expressions的第3段规定了运算符的地址可以被视为常数表达式的条件(如何允许第2段中详述的核心常数表达式变形为"真实"常数表达式):

地址常量表达式是指针类型的prvalue核心常量表达式,其计算结果为具有静态存储持续时间的对象的地址,函数的地址,空指针值,rvalue核心常数表达式文字常量表达式、引用常量表达式和地址常量表达式统称为常量表达式。

由于&y不是这些东西,所以它不被认为是一个常量表达式。

获取某个对象的地址不是罪魁祸首,而是在不相关的对象上使用operator<进行指针比较。

指针上的关系运算符只指定给指向同一类或数组中对象的指针(5.9关系运算符[expr.rel],第3点和第4点。)指向不相关对象的指针的关系比较未指定

比较相等的地址而不是排序确实有效:

int x = 42;
void f()
{
        int y = 43;
        static_assert(&x != &y, "foo");
                         ^^ <--- "<" on unrelated objects is unspecified
}
int main()
{
        f();
}

实时示例

只是为了表明这与常量表达式本身无关,

void f()
{
        int y[2] = { 42, 43 };
        static_assert(&y[0] < &y[1], "foo");
                            ^ <--- "<" on objects within an array is specified
}
int main()
{
        f();
}

另一个实例

5.19p2没有定义常量表达式,它定义了核心常量表达式

核心常量表达式只有在符合5.19p3中的规则之一的情况下才成为常量表达式。在那里,jrok已经指出了相关部分:

地址常量表达式是指针类型的prvalue核心常量表达式,其计算结果为具有静态存储持续时间的对象的地址、函数的地址或空指针值,或std::nullptr_t类型的prvalue核心常量表达式。

您的核心常量表达式&y不计算为任何一个,因此它不是地址常量表达式,因此也不是常量表达式

对不起,我同意之前的答案可能是对项目的错误解读。相反,实际的相关条款是5.19[expr.const]第3段,内容如下(增加了高亮):

文字常量表达式是文字类型的prvalue核心常量表达式,但不是指针类型。一整型常量表达式是整型或无范围枚举类型的文字常量表达式。[注:这样的表达式可以用作数组边界(8.3.4、5.3.4)、位字段长度(9.6)、枚举器初始化器如果基础类型不是固定的(7.2),则作为空指针常量(4.10)和对齐(7.6.2)。--end注意]类型T的转换常量表达式是隐式转换为类型T的字面常量表达式,其中在文字常量表达式和隐式转换中允许隐式转换(如果有的话)序列仅包含用户定义的转换、左值到右值的转换(4.1)、积分提升(4.5),以及除窄化转换(8.5.4)以外的积分转换(4.7)。[注:可使用此类表达式作为事例表达式(6.4.2),如果基础类型是固定的,则作为枚举器初始值设定项(7.2),以及作为积分或枚举非类型模板参数(14.3)。--结束注释]引用常量表达式是一个左值核心常量表达式,用于指定具有静态存储持续时间或函数的对象地址常量表达式是指针类型的prvalue核心常量表达式,计算结果为静态存储持续时间为的对象、函数地址、空指针值或prvalue核心std::nullptr_t类型的常量表达式集合,文字常量表达式,引用常量表达式和地址常量表达式被称为常量表达式

核心常量表达式并不是直接的常量表达式,第三段中列出了一些附加条件。

在C++之前的C++11中:

其他表达式[积分常数表达式]为仅为非局部静态对象初始化(3.6.2)常数表达式应评估为以下值之一:

[…]

--地址常数表达式

[…]

地址常量表达式是指向左值的指针指定静态存储持续时间的对象,字符串文字(2.13.4)或函数。

由于y没有静态存储持续时间,因此&y不会是一个常量表达式。

C++11似乎已经改变了这一点;我怀疑这是然而,这太过分了。(C++在C++11之前列出了常量表达式。C++11列出了没有的东西。它一个人很容易被遗忘。)

无论如何,当然:你的比较在标准中是不可用的C++比较两个不指向的地址的结果进入同一对象是未指定的。(另一方面,我偶尔在机器相关代码中使用类似的东西。不是静态的,而是在PC或Solaris上的Linux等平台上,可以确定指针是否指向对象具有静态寿命和自动变量,或动态用这样的技巧分配内存。)

编辑:

paxdiablo的翻译引用了我在里面找不到的一段话我对C++11的阅读;C++11遵循与C++pre-11相同的规则在这方面,为了成为一个常量地址表达式,地址必须是具有static的对象的地址生存期(或函数或空指针)。