Can statically unreachable calls cause undefined reference errors?
Asked Answered
B

3

5

Consider the following code, which has an unreachable call to undefinedFunction.

void undefinedFunction();

template <bool b = false>
void foo()
{
  static_assert(b == false);
  if (b)
    undefinedFunction();
}

int main()
{
  foo();
}

GCC compiles and links this without complaint. With the static_assert, it's hard to see how the compiler could do anything different, but does the standard have anything to say about this? What if the static_assert is removed? Is the compiler at all obligated to remove the branch or can it actually emit an unreachable call instruction that will cause the linker to complain?

Borszcz answered 8/4, 2016 at 22:3 Comment(8)
Determining unreachable code in generic case is not a trivial task...Strew
Removing dead code is the job of the optimizer, not the code generator. It you did not turn it on then, you betcha, you'll get the linker to complain. The standard does not prescribe how an optimizer should do its job. Btw, hard to guess why you can't see this yourself, tinker with the -O option.Conjectural
I believe this is a bug. This code shouldn't compile since when the template function foo is instantiated undefinedFunction is odr-used. Doesn't matter if branch won't be evaluated cause of b == false.Anthracnose
@Hans, I did try with and without optimizations and gcc succesfully builds in both cases, but your point makes sense.Borszcz
@HansPassant Optimizer doesn't play any role here.Anthracnose
@101010: It should compile, the function is declared. It should not link as there is no definition.Strew
@Strew sorry, I meant not link. I'm one of those that consider compile + linking == compile :)Anthracnose
It may not link - but the program is ill formed, no diagnostic required, so it may also appear to work just fine.Pappy
A
4

According to the C++ standard §3.2/p4 One-definition rule [basic.def.odr] (emphasis mine):

Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program; no diagnostic required. The definition can appear explicitly in the program, it can be found in the standard or a user-defined library, or (when appropriate) it is implicitly defined (see 12.1, 12.4 and 12.8). An inline function shall be defined in every translation unit in which it is odr-used.

The template function foo is instantiated undefinedFunction is odr-used (i.e., a definition of undefinedFunction is required). It doesn't matter if if clause is not evaluated. Consequently, the program is ill formed and since no diagnostic is required it may link or it may not.

Anthracnose answered 8/4, 2016 at 22:35 Comment(0)
C
7

Normally, calling a function odr-uses it, with two exceptions: if it is pure virtual, or if it is not potentially evaluated ([basic.def.odr]/5). An expression is potentially evaluated unless it is an unevaluated operand or a subexpression thereof ([basic.def.odr]/2). Unevaluated operands occur in typeid, sizeof, noexcept, and decltype, none of which applies here. Therefore undefinedFunction is odr-used as long as foo is instantiated, which it is. There is no exception for "statically unreachable" code.

Colloquialism answered 8/4, 2016 at 22:33 Comment(6)
Nice summary that possible symbols must be defined. If I'm not mistaken, it's impossible to determine if a code is unreachable in every scenario, so it's ~unguarantible~Finespun
Thank you, excellent explanation - wish I could accept two answers.Borszcz
"Normally, calling a function odr-uses it, with two exceptions: if it is pure virtual".. this is wrong. Even if it is pure, it needs to be defined if called. That is only logical: how could the compiler know what the function does, otherwise?Apprentice
@JohannesSchaub-litb If all calls dispatch to the final overrider then the pure virtual function doesn't need a definition. I am sure you know this as well as I do. I tried to keep the answer brief, so I didn't mention the exception for when the pure virtual function is called with explicit qualification.Colloquialism
@Brian OK, so the meaning of "calling a function foo" in your answer is "naming a function foo in a function call". I was not aware of this, I apologize. For non-pure virtual functions though, it is not the fact that you mentioned it in the expression that makes it need a definition though: They need a definition nontheless. The naming will not affect this in any way. In this way, your wording confused me because the "pure" there seems to say that the "pure" affects the treatment within a function call. But it instead affects the a-priori need of a definition at large.Apprentice
BTW, relevant here regarding the "as long as foo is instantiated: open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1209 . You may want to add this link to the answer. Both because it may be interesting and so that I can undo my downvote. CheersApprentice
A
4

According to the C++ standard §3.2/p4 One-definition rule [basic.def.odr] (emphasis mine):

Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program; no diagnostic required. The definition can appear explicitly in the program, it can be found in the standard or a user-defined library, or (when appropriate) it is implicitly defined (see 12.1, 12.4 and 12.8). An inline function shall be defined in every translation unit in which it is odr-used.

The template function foo is instantiated undefinedFunction is odr-used (i.e., a definition of undefinedFunction is required). It doesn't matter if if clause is not evaluated. Consequently, the program is ill formed and since no diagnostic is required it may link or it may not.

Anthracnose answered 8/4, 2016 at 22:35 Comment(0)
F
1

As others commented, two separate steps contribute to this. The unreachable code may be erased by the optimizer, so the link for undefinedFunction() is never requested. The compilation step does not care for symbols that are not defined (more information about compilation in this community answer).

This is independent of the static_assert. You can have undefined references in template code that never gets initialized, and the compilation succeeds, as the compiler never considers the code, it never emit requirement for the link.

If the symbol gets through, and is requested in some later step, linkage will fail. This is the same that happens when you compile a library with template classes, and later tries to use a template with argument types for which the class was not explicitly initialized, you will get undefined references to types using a library that compiled fine on it's own.

If you wish to exam if you compiler is actually eliminating dead code, this answer details about profiling dead code in GCC.

Finespun answered 8/4, 2016 at 22:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.