为什么链接器不会在下面的代码中发出错误?

Why doesn't the linker emit an error in the code below?

我找到了下面的示例 。显然,代码片段中的注释是错误的,因为变量 S::x 被表达式 &S::x.

ODR 使用
struct S { static const int x = 1; };
void f() { &S::x; } // discarded-value expression does not odr-use S::x
int main(){
    f();
}

live example

我知道编译器不需要发出这样的错误,因为 [basic.def.odr]/10 说 "no diagnostic required"。但是为什么链接器没有像下面的代码那样发出关于未定义变量 S::x 的错误?

#include<iostream>
struct S { static const int x = 1; } s;

int main() {
    std::cout << &s.x << '\n';
}  

live example

你问:

[Why] doesn't the linker emit an error?

一边说:

[basic.def.odr]/10 says "no diagnostic required"

您在 "no diagnostic required" 中回答您自己的问题。不遵守 odr 规则是 Undefined Behavior, the linker could raise an error or build a 4D version of Tetris 并且按照规格仍然可以!


并澄清 &S::x 是否使用 odr x:

A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion to x yields a constant expression that does not invoke any non-trivial functions and, if x is an object, ex is an element of the set of potential results of an expression e, where either the lvalue-to-rvalue conversion is applied to e, or e is a discarded-value expression.

对象的地址永远不是常量表达式。 S::x 用于 &S::x

为了证明最后的断言:

[expr.const]/6

A constant expression is either a glvalue core constant expression that refers to an entity that is a permitted result of a constant expression (as defined below), or a prvalue core constant expression whose value satisfies the following constraints [...]

[expr.const]/2.7

2) An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:
[...]
2.7) an lvalue-to-rvalue conversion unless it is applied to

(none 以下要点适用:)

2.7.1) a non-volatile glvalue of integral or enumeration type that refers to a complete non-volatile const object with a preceding initialization, initialized with a constant expression, or
2.7.2) a non-volatile glvalue that refers to a subobject of a string literal, or
2.7.3) a non-volatile glvalue that refers to a non-volatile object defined with constexpr, or that refers to a non-mutable subobject of such an object, or
2.7.4) a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of e;

Clearly the comment in the snippet was wrong as the variable S::x is odr-used by the expression &S::x.

然后那个表情被放到位地板上。 &S::x; 未使用 ODR。 [basic.def.odr] 第 3 段的变体如下(粗体强调我的):

A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion to x yields a constant expression that does not invoke any non-trivial functions and, if x is an object, ex is an element of the set of potential results of an expression e, where either the lvalue-to-rvalue conversion is applied to e, or e is a discarded-value expression.

[expr] 部分第 11 段介绍了丢弃值表达式的概念:

In some contexts, an expression only appears for its side effects. Such an expression is called a discarded-value expression. The expression is evaluated and its value is discarded. The array-to-pointer and function- to-pointer standard conversions are not applied. The lvalue-to-rvalue conversion is applied if and only if the expression is a glvalue of volatile-qualified type and it is one of the following:

  • ( expression ), where expression is one of these expressions,
  • id-expression,
  • subscripting,
  • class member access,
  • indirection,
  • pointer-to-member operation,
  • conditional expression where both the second and the third operands are one of these expressions, or
  • comma expression where the right operand is one of these expressions.

语句&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++ compiler/linker 编译(但带有关于丢弃表达式的警告),但使用 --std=c++11 或更高版本无法 compile/link。 C++11 添加了很多关于 C++ 抽象机工作的内容。尽管 S::x; 仍然是一个废弃的值表达式,但 S::x 现在是易变的,需要编译器在将结果放到位层之前应用左值到右值的转换。

将上面的 S::x 更改为 &S::x,程序再次编译(但再次出现关于丢弃表达式的警告)。尽管 S::x 是可变的,但它的地址不是。

But why doesn't the linker emit an error about the undefined variable S::x, as it does in the code below?

因为简直优化掉了!结果从未使用且没有副作用的表达式将被忽略。并且不能链接被忽略的内容。根本没有引用该变量的代码,即使它的地址已被获取但随后未被使用。

如您的 wandbox 示例所示,编译器发出正确的诊断信息: "expression result unused"。

未使用的代码以后不会导致链接器错误 ;)

您的第二个示例使用值( var 的地址),因此需要计算表达式。这会向链接器发出代码,其中无法在任何地方找到地址符号。