5

I found the example below here. Clearly the comment in the snippet was wrong as the variable S::x is odr-used by the expression &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();
}

See live example

I understand the compiler doesn't need to emit such an error because [basic.def.odr]/10 says "no diagnostic required". But why doesn't the linker emit an error about the undefined variable S::x, as it does in the code below?

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

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

See live example.

João Afonso
  • 1,934
  • 13
  • 19
  • Possible duplicate of https://stackoverflow.com/questions/47952024/why-is-sx-not-odr-used – bartop May 29 '18 at 13:31
  • @bartop might be, but that question is unanswered. – JHBonarius May 29 '18 at 13:32
  • 2
    _"why doesn't the linker emit an error"_ that _is_ a diagnostic. On the implementation side, an expression that doesn't compile down to a read/write probably won't trigger a linker error – Passer By May 29 '18 at 13:43
  • Defining `static x` outside the struct as it should works in both cases https://ideone.com/9OzA0H – Killzone Kid May 29 '18 at 13:44
  • 5
    The linker is formally part of the implementation - the C++ standard doesn't require a diagnostic from the linker any more than it does from a compiler. In the first example, since the result of `&S::x` is not used, the compiler may not emit code which accesses/references it - which, in turn, means the linker is not required to link any references to `S::x` to an actual variable definition. In the second case, the result of expression `&s.x` IS used (its value is printed), so the compiler emits code that uses `S::x`, which in turn means the compiler needs to resolve the references. – Peter May 29 '18 at 14:04
  • 1
    @Peter Variable `S::x`is odr-used by the expression `S::x`, and thus, by the expression `&S::x` because it doesn't satisfy the "unless" condition in [\[basic.def.odr\]/4](http://eel.is/c++draft/basic.def.odr#4). – João Afonso May 29 '18 at 18:59
  • 1
    @JoãoAfonso Peter (who knows what odr-use is) didn't write _"is not odr-used"_ but _"is not used"_. I think he's speaking in a practical point of view here. – YSC May 29 '18 at 20:38

3 Answers3

2

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

Because it is simply optimized out! An expression which result is never used and has no side effect will simply be ignored. And what was ignored must not be linked in. There is simply no code which reference the variable, also if the address of it was taken but then is not used.

As seen in your wandbox example, the compiler emits the correct diagnostic: "expression result unused".

Code which is not in use will not result later in linker errors ;)

Your second example uses the value ( address of var ) and so there is a need to evaluate the expression. That emits code to the linker where the symbol of the address can not be found anywhere.

Klaus
  • 24,205
  • 7
  • 58
  • 113
  • But why does the compiler optimizes out some variable from a code that is ill-formed? Both snippets above are ill-formed. What do you prefer? The first one, that doesn't tell you anything about the problem, or the second that emits an error message telling you that something is wrong with your code? – João Afonso May 29 '18 at 21:40
  • @JoãoAfonso No program, even a sophisticated one like a compiler/linker, could diagnose all undefined behaviors. I suspect that problem to be [undecidable](https://en.wikipedia.org/wiki/Undecidable_problem). The Standard chose to only impose a diagnostic on easy-to-spot violations. This also allow the implementation to make some optimization by simply assuming UB cannot happen (since if it does happen, the Standard imposes no requirement). – YSC May 29 '18 at 21:57
  • It's optimized away because forming that pointer-to-member doesn't affect the observable behaviour of the program, and optimizing away redundant code with no side effects produces faster programs. The standard explicitly says no diagnostic is required, so the compiler is not required to give an error, so producing a faster executable is what most users prefer their compiler to do. Modern compilers go to huge effort to remove useless code and produce faster executables, and so if the call to `f()` can be removed then of course that's what the compiler will do. – Jonathan Wakely May 29 '18 at 21:59
  • @JonathanWakely Then, how do you explain the optimization with [this](http://coliru.stacked-crooked.com/a/e5b3c66648c39368) ill-formed code? – João Afonso May 29 '18 at 22:59
  • That is a very very simple optimisation. The compiler knows that the value of `a.k` is 1 so it can simply pass that immediate value in a register instead of loading it from the memory location `A::k` – Jonathan Wakely May 29 '18 at 23:56
  • @JonathanWakely My last question. Where in the Standard does it say that these optimizations are allowed? For me, the observable behavior of an ill-formed program like the one [here](http://coliru.stacked-crooked.com/a/621e5ef6a4379261), is best shown by its ill-formedness. – João Afonso May 30 '18 at 00:32
0

You ask:

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

While saying:

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

You answer your own question in "no diagnostic required". Not complying to the odr-rule is Undefined Behavior, the linker could raise an error or build a 4D version of Tetris and it still would be OK by the specs!


And to clarify about &S::x odr-using or not 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.

The address of an object is never a constant expression. S::x is odr-used in &S::x.

To justify that last assertion:

[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 [...]

and

[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 of the followinf points applies:)

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;

YSC
  • 38,212
  • 9
  • 96
  • 149
  • "no diagnostic required" from the compiler, **not** the linker.The second code above could also be said to show undefined behavior, but the linker emitted an error, which is much better than not complaining. – Alexander May 29 '18 at 15:02
  • 3
    @Alexander, the C++ standard doesn't say anything about "compiler" or "linker", just "the implementation". Claiming that "no diagnostic required" only applies to the compiler not the linker has no basis in the C++ standard. – Jonathan Wakely May 29 '18 at 16:10
  • @JonathanWakely There are 12 occurences of the word "compiler" in the standard, versus 1 ocurrence of the word "linker" in a footnote. – Alexander May 29 '18 at 17:54
  • @JonathanWakely See also the second bullet point in [this answer](https://stackoverflow.com/a/24894943/5479741) in SO. – Alexander May 29 '18 at 18:29
  • 1
    @Alexander every single one of those is a non-normative note, i.e. purely informative not part of the requirements or specification of the standard. Are you suggesting that the linker _is_ required to give diagnostics for "ill-formed; no diagnostic required" ODR violations? If not, what are you saying? – Jonathan Wakely May 29 '18 at 18:34
  • @JonathanWakely Where is a normative clause saying the contrary, i.e., that NDR applies to the compiler and the linker? – Alexander May 29 '18 at 18:44
  • 1
    @Alexander [intro.compliance] which talks about conforming implementations, not about compilers and linkers. Specifically, _"If a program contains a violation of a rule for which no diagnostic is required, this document places no requirement on implementations with respect to that program."_ Because as I already said, there is no normative wording that distinguishes "compiler" and "linker". It's all "the implementation". – Jonathan Wakely May 29 '18 at 18:57
  • @Alexander Don't forget that C++ can be an interpreted language and such an interpreter would still be in conformance with the Standard ;) – YSC May 29 '18 at 20:17
  • 3
    @Alexander: There's no need to "say the contrary" since, as it has already been stated, normative language does not even include such concepts as "compiler" or "linker". C++ implementations are atomic. They work in phases (see 5.2), but these language rules are not phase-specific in any way. – AnT stands with Russia May 29 '18 at 20:50
  • @AnT Let's assume I'm wrong, i.e., that NDR applies to both compiler and linker. But the question is still valid, as it is much easier for the linker to detect such an error after all TU's have been compiled. Isn't that true? – Alexander May 29 '18 at 21:08
  • 1
    @Alexander no, not always. If the compiler optimizes away all uses of an object (by substituting its value everywhere it's needed, or deciding it isn't needed anyway because it's only referred to in functions with no side effects) then the linker never even sees a reference to the object and so won't complain if its definition is missing. The linker only complains about undefined references to things that the compiler emits a reference to. If the compiler doesn't output it, the linker knows nothing about it whatsoever. – Jonathan Wakely May 29 '18 at 21:42
  • @JonathanWakely Could you answer the question I posted above in [my comment](https://stackoverflow.com/questions/50585644/why-doesnt-the-linker-emit-an-error-in-the-code-below#comment88196020_50592942) to Klaus? – João Afonso May 29 '18 at 21:53
  • You are misinterpreting the sub points 2.7.1 to 2.7.4. Those are the conditions under which an implementation must evaluate a discarded value expression, and doing so is only needed in the case of volatility. There's nothing volatile about the address of an object, even if that object itself is volatile. The address of an object is constant throughout the lifetime of the object. – David Hammen May 30 '18 at 11:35
  • That's not why I downvoted, however. I downvoted for "*The address of an object is never a constant expression*". The address of an object is either a constant expression or it is ill-formed. It is ill-formed if the object in question doesn't exist (e.g, `&(x->y)`, where `x` is the null pointer), and no diagnostic is required in this case. If the object does exist, it's address does not change during the object's lifetime; i.e., it's constant. Unlike Java, C++ does not dynamically relocate objects during their lifetime. – David Hammen May 30 '18 at 11:50
  • @DavidHammen "_&(x->y), where x is the null pointer_" is not ill formed, if `x` has the correct type – curiousguy Jun 11 '18 at 01:58
-1

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

And then that expression is dropped on the bit floor. &S::x; is not ODR-used. Once variant of [basic.def.odr] paragraph 3 reads (bold emphasis mine):

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.

The section[expr] paragraph 11 covers the concept of a discarded-value expression:

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.

The statement &S::x; is a discarded-value expression that does not require lvalue-to-rvalue conversion, so there's nothing to convert. The statement might as well not exist, and hence doesn't exist as far as ODR-usage is concerned.

This metric can be changed by qualifying S::x as volatile rather than as const, and by trying to drop S::x on the bit floor:

struct S { static volatile int x; };
void f() { S::x; } // This discarded-value expression does odr-use S::x
int main(){
    f();
}

The above compiles (but with a warning about a discarded expression) with GNU's C++ compiler/linker using --std=c++03 but fails to compile/link using --std=c++11 or higher. C++11 added quite a bit regarding the workings of the C++ abstract machine. Even though S::x; remains a discarded value expression, that S::x is now volatile requires the compiler to apply lvalue-to-rvalue conversion prior to dropping the result on the bit floor.

Change the S::x in the above to &S::x and the program once again compiles (but once again with a warning about a discarded expression). Even though S::x is volatile, it's address is not.

Community
  • 1
  • 1
David Hammen
  • 32,454
  • 9
  • 60
  • 108
  • 2
    The variable `S::x` **is** odr-used by the expression `&S::x` because it doesn't satisfy the "unless" condition in [\[basic.def.odr\]/4](http://eel.is/c++draft/basic.def.odr#4) – João Afonso May 29 '18 at 18:09
  • Indeed, see the note added to my answer. – YSC May 29 '18 at 20:23