user1196549
user1196549

Reputation:

Can a constant be optimized away

If we have a static const class member the address of which is never used, can the member be optimized away and allocated no storage ?

Upvotes: 2

Views: 822

Answers (2)

Bitwize
Bitwize

Reputation: 11230

The answer is it depends on context.

The compiler must follow the as-if rule, which ensures that the program must behave the same way optimized as it would unoptimized -- so it can only remove constants that it knows will not affect the behavior of syntactically valid code.

Short Version

The short version is that constants can only be optimized out if they fit the following criteria:

  • Constants are trivial, or have visible constructors / destructors that do not affect external state,
    • This effect is transitive. If a constructor / destructor calls a non-visible function, the compiler must assume that the call may alter external state, and is thus significant.
  • Constants are not ODR used, and
  • Constants are either anonymously defined (in an unnamed namespace), or inline defined

Long Version

There are several cases that will affect storage backing

  1. Whether the constant has been given explicit storage backing,
  2. Whether the constant exists in an unnamed namespace,
  3. Whether the constant has non-trivial, non visible constructors/destructors,
  4. Whether the constant is defined inline (C++17),
  5. Whether the constant is defined inline (C++17) and ODR used,
  6. Whether the constant is defined in-line, but not given storage-backing

Lets break down these different sections:

1. Constant is given explicit storage backing

If you define storage backing from the start, e.g.:

struct example {
    static const int value;
}; 
const int example::value = 5;

Then there will usually be storage backing, since the compiler has to assume it will be ODR-used eventually -- even if the constant is private.

Example on Compiler Explorer

2. Constant is in an unnamed namespace

However, if these types are defined in an unnamed namespace, which makes them anonymous symbols for the translation unit -- then lack-of-use may result in it being optimized away:

namespace {
    struct example {
        static const int value;
    }; 
    const int example::value = 5;
}

Example on Compiler Explorer.

This will only work for trivial constructors / destructors, or constructors / destructors that the compiler can currently see in order to optimize out instructions.

3. Constant is in an unnamed namespace with non-trivial constructor / destructor

If the constant above has a non-trivial constructor or destructor, or a constructor/destructor that is not visible, the constants will have to be emitted:

Either:

namespace {
struct actor{ 
    actor();  // not defined
    ~actor(); // not defined
};

class example {
    static const actor value;
};
const actor example::value{};
}

Example on Compiler Explorer

or:

extern int some_global;
namespace {
struct actor{ 
    actor(){ ::some_global = 5;} 
    ~actor(){ ::some_global = 10;}
};

class example {
    static const actor value;
};
const actor example::value{};
}

Example on Compiler Explorer

4. Constant is defined inline (C++17)

If you define an inline static const or inline static constexpr value in C++17, then it depends on whether it is ODR-used for whether the symbol will ever be emitted.

No ODR-use -- No emission:

struct example {
    inline static const int value = 5;
};

void test()
{
    std::printf("%d", example::value);
}

Example on Compiler Explorer.

5. Constant is defined inline (C++17) with ODR use

If the symbol is inline but is ODR used, the storage backing must be emitted.

ODR use -- Emission:

struct example {
    inline static const int value = 5;
};

void consume(const int&);

void test()
{
    consume(example::value)
}

Example on Compiler Explorer.

6. Constant is defined in-line, but not given storage-backing

In the case that you have an inline definition of a static const object (without C++17's inline), there should never be storage backing. The constants can only be created by constant expressions (with or without constexpr), but have no explicitly stated storage-backing -- meaning that they cannot be ODR-used unless declared inline.

This is what is leveraged by C++'s type-traits, since they do not produce any additional code since they amount to compile-time constant objects.

struct example {
    static const int value = 5;
};

Example on Compiler Explorer


Edit: One more note that I wanted to add: If there is any storage-backing specified to a symbol with non-internal linkage, either through explicit definitions or via ODR use with an inline symbol, the compiler/linker should not be able to optimize this backing out. At least, not from a proper interpretation of the as-if rule (though certain non-standard optimization flags may permit this).

The issue is that the compiler must assume that symbols with external linkage may still be named or referenced elsewhere, even if the symbol is private and never accessible via friend-ship. C++ has sharp corners where you can reference private members in template parameters via explicit template specializations. So even though it should logically not be accessible, it actually is from the language (and thus, compiler's) view.

Upvotes: 3

Asteroids With Wings
Asteroids With Wings

Reputation: 17464

Yes, but only link-time optimisation is capable of doing this, because only the link stage can prove that the address is never taken throughout the whole program. And, if you're producing a shared library, that's just off the table. Speaking generally, you're best off assuming that the object probably will take up space in your executable.

Regardless of whether storage is provided, though, you can certainly expect the compiler to "inline" use of the value, if the initialiser is visible in the same translation unit. You can observe this by failing to provide a separate definition, then doing anything that doesn't involve ODR-use and noticing that you may not get an undefined reference link error.

Upvotes: 2

Related Questions