我在here下面找到了这个例子。显然,片段中的注释是错误的,因为变量S::x
被表达式&S::x
使用。
struct S { static const int x = 1; };
void f() { &S::x; } // discarded-value expression does not odr-use S::x
int main(){
f();
}
我知道编译器不需要发出这样的错误,因为[basic.def.odr]/10说“不需要诊断”。但是为什么链接器不会发出关于未定义变量S::x
的错误,就像在下面的代码中那样?
#include<iostream>
struct S { static const int x = 1; } s;
int main() {
std::cout << &s.x << '\n';
}
但是为什么链接器不会发出关于未定义变量S :: x的错误,就像在下面的代码中一样?
因为它只是简单优化了!从不使用结果且没有副作用的表达式将被忽略。被忽略的东西一定不能被联系起来。根本没有引用变量的代码,如果它的地址被采用但是没有被使用。
如您的wandbox示例所示,编译器会发出正确的诊断:“表达式结果未使用”。
未使用的代码以后不会导致链接器错误;)
您的第二个示例使用值(var的地址),因此需要评估表达式。这会向链接器发送代码,其中无法在任何地方找到地址的符号。
你问:
[为什么]链接器不发出错误?
虽然说:
[basic.def.odr]/10说“不需要诊断”
您可以在“无需诊断”中回答您自己的问题。不遵守odr规则是Undefined Behavior,链接器可能引发错误或构建4D version of Tetris,它仍然可以通过规格!
并澄清关于&S::x
使用或不使用x
:
变量
x
,其名称显示为潜在评估表达式ex
,由ex
使用,除非将lvalue-to-rvalue转换应用于x
,产生一个不调用任何非平凡函数的常量表达式,如果x是对象,ex是表达式e的潜在结果集合的元素,其中左值到左值的转换应用于e,或者e是丢弃值表达式。
对象的地址永远不是常量表达式。 S::x
在&S::x
中使用过。
为了证明最后的断言:
[expr.const]/6
常量表达式是glvalue核心常量表达式,它指的是一个实体,它是一个常量表达式的允许结果(如下定义),或一个prvalue核心常量表达式,其值满足以下约束条件[...]
和
[expr.const]/2.7
2)表达式
e
是核心常量表达式,除非根据抽象机器的规则评估e
将评估以下表达式之一: [...] 2.7)左值到右值的转换,除非它适用于
(以下几点均不适用:)
2.7.1)一个非整数或枚举类型的非易失性glvalue,它引用一个完整的非易失性const对象,具有前面的初始化,用常量表达式初始化,或者 2.7.2)一个非易失性glvalue,它引用字符串文字的子对象,或 2.7.3)非易失性glvalue,它指的是用constexpr定义的非易失性对象,或者指的是这种对象的非可变子对象,或者 2.7.4)文字类型的非易失性glvalue,指的是一个非易失性对象,其生命周期始于
e
的评估范围内;
显然,片段中的注释是错误的,因为变量
S::x
被表达式&S::x
使用。
然后那个表达式被丢弃在位底上。 &S::x;
不是ODR使用的。一旦[basic.def.odr]
第3段的变体读取(大胆强调我的):
变量
x
的名称显示为潜在评估的表达式ex
由ex
使用,除非将lvalue-to-rvalue转换应用于x
,产生一个不调用任何非平凡函数的常量表达式,如果x
是一个对象,ex
是表达式e
的潜在结果集合的元素,其中左值到右值的转换应用于e
,或者e
是丢弃值表达式。
section[expr]
第11段涵盖了废弃值表达的概念:
在某些情况下,表达式仅出现其副作用。这种表达式称为丢弃值表达式。计算表达式并丢弃其值。不应用数组到指针和函数到指针的标准转换。当且仅当表达式是volatile限定类型的glvalue并且它是以下之一时,才应用左值到右值转换:
- (表达式),其中expression是这些表达式之一,
- ID-表达,
- 下标,
- 班级成员访问,
- 间接,
- 指向成员的操作,
- 条件表达式,其中第二个和第三个操作数都是这些表达式之一,或者
- 逗号表达式,其中右操作数是这些表达式之一。
语句&S::x;
是一个丢弃值表达式,不需要左值到右值的转换,因此无需转换。该声明可能也不存在,因此就ODR使用而言并不存在。
这个指标可以通过将S::x
定义为volatile
而不是const
来改变,并试图在位底上放下S::x
:
struct S { static volatile int x; };
void f() { S::x; } // This discarded-value expression does odr-use S::x
int main(){
f();
}
上面使用--std=c++03
编译(但有关于丢弃表达式的警告)与GNU的C ++编译器/链接器,但无法使用--std=c++11
或更高版本编译/链接。关于C ++抽象机的工作原理,C ++ 11增加了很多。尽管S::x;
仍然是一个丢弃的值表达式,但S::x
现在是volatile,要求编译器在将结果丢弃到位底之前应用左值到右值的转换。
将上面的S::x
更改为&S::x
并再次编译该程序(但再一次警告丢弃的表达式)。即使S::x
是易变的,它的地址也不是。