17

I have the following piece of code:

#include <stdio.h>

typedef struct {
    bool some_var;
} model_t;

const model_t model = {
    true
};

void bla(const model_t *m) {
    if (m->some_var) {
        printf("Some var is true!\n");
    }
    else {
        printf("Some var is false!\n");
    }
}

int main() {
    bla(&model);
}

I'd imagine that the compiler has all the information required to eliminate the else clause in the bla() function. The only code path that calls the function comes from main, and it takes in const model_t, so it should be able to figure out that that code path is not being used. However:

Without inline

With GCC 12.2 we see that the second part is linked in.

If I inline the function this goes away though:

With inline

What am I missing here? And is there some way I can make the compiler do some smarter work? This happens in both C and C++ with -O3 and -Os.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Jan Jongboom
  • 26,598
  • 9
  • 83
  • 120
  • 15
    The compiler cannot optimize the else path away as the object file might be linked against any other code. This would be different if the function would be static. – MrTux Aug 29 '22 at 09:40
  • 4
    If you make `bla` `static`, compiler may optimize it. – Özgür Murat Sağdıçoğlu Aug 29 '22 at 09:42
  • 9
    The compiler did inline the `bla` call in `main` and optimized the `else` branch away there. It just has to emit the function in case another translation unit uses it. (Compiler explorer is showing you the output after compilation before linking.) – user17732522 Aug 29 '22 at 09:44
  • @user17732522 How do you knew this? It is not clear from what the OP has posted. – Zakk Aug 29 '22 at 13:16
  • 4
    @Zakk What are you referring to? That `bla` was inlined? The image in the question shows the assembly output from the compiler. In the emitted `main` function there is no call to `bla`, just a call to `puts` with its argument loaded unconditionally from `.LC0` which is the string in the first branch of the `if`. So the branching was optimized away. – user17732522 Aug 29 '22 at 13:19
  • FYI, in C++ you don't need to do the `typedef struct`. The name of the struct can be used as a type name. – Thomas Matthews Aug 29 '22 at 16:57
  • Since you tagged C++, prefer to pass by reference instead of pointers. Pointers can point to *anywhere* including invalid locations. – Thomas Matthews Aug 29 '22 at 16:59
  • 2
    Your images should be https://godbolt.org/ full links, not just to the image on imgur! Code shouldn't be posted as images in the first place, though. – Peter Cordes Aug 30 '22 at 14:57

3 Answers3

33

The compiler does eliminate the else path in the inlined function in main. You're confusing the global function that is not called anyway and will be discarded by the linker eventually.

If you use the -fwhole-program flag to let the compiler know that no other file is going to be linked, that unused segment is discarded:

[See online]

Enter image description here

Additionally, you use static or inline keywords to achieve something similar.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Aykhan Hagverdili
  • 28,141
  • 6
  • 41
  • 93
27

The compiler cannot optimize the else path away as the object file might be linked against any other code. This would be different if the function would be static or you use whole program optimization.

MrTux
  • 32,350
  • 30
  • 109
  • 146
  • 8
    If the function was declared `static` it would also simply not be emitted (same as with `inline`). All call sites are inlined anyway. The emitted function in the question was never used. – user17732522 Aug 29 '22 at 09:47
8

The only code path that calls the function comes from main

GCC can't know that unless you tell it so with -fwhole-program or maybe -flto (link-time optimization). Otherwise it has to assume that some static constructor in another compilation unit could call it. (Including possibly in a shared library, but another .cpp that you link with could do it.) e.g.

// another .cpp
typedef struct {  bool some_var; } model_t;
void bla(const model_t *m);              // declare the things from the other .cpp

int foo() {
    model_t model = {false};
    bla(&model);
    return 1;
}
int some_global = foo();  // C++ only: non-constant static initializer.

Example on Godbolt with these lines in the same compilation unit as main, showing that it outputs both Some var is false! and then Some var is true!, without having changed the code for main.

ISO C doesn't have easy ways to get init code executed, but GNU C (and GCC specifically) have ways to get code run at startup, not called by main. This works even for shared libraries.


With -fwhole-program, the appropriate optimization would be simply not emitting a definition for it at all, as it's already inlined into the call-site in main. Like with inline (In C++, a promise that any other caller in another compilation unit can see its own definition of the function) or static (private to this compilation unit).

Inside main, it has optimized away the branch after constant propagation. If you ran the program, no branch would actually execute; nothing calls the stand-alone definition of the function.


The stand-alone definition of the function doesn't know that the only possible value for m is &model. If you put that inside the function, then it could optimize like you're expecting.

Only -fPIC would force the compiler to consider the possibility of symbol-interposition so the definition of const model_t model isn't the one that is in effect after (dynamic) linking. But you're compiling code for an executable not a library. (You can disable symbol-interposition for a global variable by giving it "hidden" visibility, __attribute__((visibility("hidden"))), or use -fvisibility=hidden to make that the default).

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847