下面的代码编译在GCC,clang和VS2017以及表达式中 a->i 在里面 return 语句被其常量值1替换。说这是有效的是正确的,因为 a 是 不 在表达中使用的odr a->i?。
struct A
{
static const int i = 1;
};
int f()
{
A *a = nullptr;
return a->i;
}
PS:我相信 a 是 不 在表达中使用的odr a->i 因为它满足了“除非”的条件 [basic.def.odr / 4, 如下:
一个变量 x 其名称显示为可能已评估的名称
表达 ex 是有用的 ex 除非 应用
左值到右值的转换(7.1)到 x 产生一个常数表达式
(8.6)不引用任何非平凡的
功能,如果 x 是一个对象, ex 是表达式的潜在结果集的一个元素 e,哪里有
应用左值到右值转换(7.1) e, 要么 e 是一个
丢弃值表达式(8.2)。
特别是表达 ex == a 是表达式的潜在结果集的一个元素 e == a->i, 根据 [basic.def.odr] / 2(2.3),包含表达式 ex,应用左值到右值转换 e。
a 由于你在“除非”的第一部分失败了,因此使用率很高:
应用左值到右值转换(7.1) x 产生一个不调用任何非平凡函数的常量表达式(8.6)
将左值到右值的转换应用于 a 不会产生常数表达式。
其余的是核心问题 315 和 232。
您的分析还有两种方式:
- “对象表达式”是使用
. 类成员访问的形式,所以你需要重写 a->i 点状,即 (*a).i,在应用[basic.def.odr] /2.3之前。 a 不是该表达式的潜在结果集的成员。
- 该子弹本身是有缺陷的,因为它是用非静态数据成员编写的。对于静态数据成员,潜在结果集实际上应该是命名的静态数据成员 - 请参阅 核心问题2353所以
a 是双重的不是该表达的潜在结果的成员。
[expr.const] /2.7:
一种表达 e 是一个 核心常数表达式 除非
评估 e遵循抽象机器的规则,会
评估以下表达式之一:
- [...]
- 除非适用,否则左值转换为右值
- 一个非整数或枚举类型的非易失性glvalue,它引用一个带有前面的完整的非易失性const对象
初始化,用常量表达式初始化,或
- 一个非易失性glvalue,引用字符串文字的子对象,或
- 非易失性glvalue,引用定义的非易失性对象
constexpr或者指的是一个不可变的子对象
这样的对象,或者
- 一个非易失性的文字类型glvalue,它引用一个非易失性对象,其生命周期始于评估范围内
e;
- [...]
i 是一个 static 班级成员......因为你可以访问 static 通过对实例使用传统方法的类成员,它们并不特别依赖于任何实例,因此您不需要取消引用 nullptr 指针(就像你使用时一样) sizeof 运营商)。您也可以使用简单的方法访问该字段
return A::i;
声明,因为您不需要创建实例来访问它。确实,存在 const,编译器允许将其作为常量值进行管理,因此只有在您需要使用它的地址的情况下(通过 & operator)编译器可以绕过在只读内存中分配它。
以下示例将探测:
#include <iostream>
struct A {
static const int i = 1;
};
int main()
{
std::cout << ((A*)0)->i << std::endl;
std::cout << A::i << std::endl;
}
将打印
$ a.out
1
1
$ _