Mixing extern and const
Asked Answered
C

6

81

Can I mix extern and const, as extern const? If yes, does the const qualifier impose it's reign only within the scope it's declared in or should it exactly match the declaration of the translational unit it's declared in? I.e. can I declare say extern const int i; even when the actual i is not a const and vice versa?

Chelsiechelsy answered 3/2, 2010 at 9:32 Comment(4)
This question isn't directly about yours, but has all the required information: https://mcmap.net/q/260595/-non-integral-constants/…Ultann
Let me mention about the difference in linking here: Using extern with const will disable const-folding and force the compiler to allocate memory for the constant, which woulnd't have been the case otherwise, wherein it will do the substitution inplace (after folding, if possible). [hence it's not advisable, and I've decided against using it :)]Chelsiechelsy
if it is a constant, why would compiler disable constant folding in case of extern?Ambassadoratlarge
@Jimm: Because when declaring extern const you don't give a initialization value (see the accepted answer) and the compiler will expect the linker to "fill in the blanks" and thereby forcing the linker to allocate space for the constant.Chelsiechelsy
R
78
  • Yes, you can use them together.
  • And yes, it should exactly match the declaration in the translation unit it's actually declared in. Unless of course you are participating in the Underhanded C Programming Contest :-)

The usual pattern is:

  • file.h:
    extern const int a_global_var;
  • file.c:
    #include "file.h"
    const int a_global_var = /* some const expression */;

Edit: Incorporated legends2k's comment. Thanks.

Radack answered 3/2, 2010 at 9:42 Comment(9)
Did you mean const int a_global_var = <some_value_here>;?Chelsiechelsy
I could not thing of the benefit of an extern const, and then it occurted to me that it will save bigtime on compile time when changing constants. ThanksLeyba
Um, since const s are implicitly static, you need an extern even on the a_global_var definition (in file.c). Without this, anything that includes file.h will not link because it is looking for a const int a_global_var with external linkage.Cilla
C++11 standard 3.5/3 (emphasis mine): A name ... has internal linkage if it is the name of a variable that is explicitly declared const or constexpr and neither explicitly declared extern nor previously declared to have external linkage;Chelsiechelsy
@user123456: No, the extern not being there is correct. If the constant is pre-declared as extern, then extern in the definition is optional. Even without an explicit extern it will define such a const object with external linkage. - https://mcmap.net/q/260595/-non-integral-constantsChelsiechelsy
However, if file.c did not include "file.h", the definition would have to include externArv
const variable can not be seen by other files implicitly unless add an explicit extern. so file.c should be extern const int a_global_var = /* expression */;Manamanacle
Original Link for Underhanded C Programming Contest now dead - and I couldn't find the reference on the Wayback machine archives of the originally linked to site. 8-(Luvenialuwana
Normally, the C++ compiler avoids creating storage for a const, but instead holds the definition in its symbol table. When you use extern with const, however, you force storage to be allocated (this is also true for certain other cases, such as taking the address of a const). Storage must be allocated because extern says "use external linkage," which means that several translation units must be able to refer to the item, which requires it to have storage.Palaeozoic
S
16

C++17 inline variables

If you think you want an extern const, then it is more likely that you would actually want to use C++17 inline variables.

This awesome C++17 feature allow us to:

  • conveniently use just a single memory address for each constant
  • store it as a constexpr: How to declare constexpr extern?
  • do it in a single line from one header

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

inline constexpr int notmain_i = 42;

const int* notmain_func();

#endif

notmain.cpp

#include "notmain.hpp"

const int* notmain_func() {
    return &notmain_i;
}

Compile and run:

g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
./main

GitHub upstream.

See also: How do inline variables work?

C++ standard on inline variables

The C++ standard guarantees that the addresses will be the same. C++17 N4659 standard draft 10.1.6 "The inline specifier":

6 An inline function or variable with external linkage shall have the same address in all translation units.

cppreference https://en.cppreference.com/w/cpp/language/inline explains that if static is not given, then it has external linkage.

Inline variable implementation

We can observe how it is implemented with:

nm main.o notmain.o

which contains:

main.o:
                 U _GLOBAL_OFFSET_TABLE_
                 U _Z12notmain_funcv
0000000000000028 r _ZZ4mainE19__PRETTY_FUNCTION__
                 U __assert_fail
0000000000000000 T main
0000000000000000 u notmain_i

notmain.o:
0000000000000000 T _Z12notmain_funcv
0000000000000000 u notmain_i

and man nm says about u:

"u" The symbol is a unique global symbol. This is a GNU extension to the standard set of ELF symbol bindings. For such a symbol the dynamic linker will make sure that in the entire process there is just one symbol with this name and type in use.

so we see that there is a dedicated ELF extension for this.

Pre-C++ 17: extern const

extern const does work as in the example below, but the downsides over inline are:

  • it is not possible to make the variable constexpr with this technique, only inline allows that: How to declare constexpr extern?
  • it is less elegant as you have to declare and define the variable separately in the header and cpp file

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.cpp

#include "notmain.hpp"

const int notmain_i = 42;

const int* notmain_func() {
    return &notmain_i;
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

extern const int notmain_i;

const int* notmain_func();

#endif

GitHub upstream.

Pre-C++17 header only alternatives

These are not as good as the extern solution, but they work and only take up a single memory location:

A constexpr function, because constexpr implies inline and inline allows (forces) the definition to appear on every translation unit:

constexpr int shared_inline_constexpr() { return 42; }

and I bet that any decent compiler will inline the call.

You can also use a const or constexpr static integer variable as in:

#include <iostream>

struct MyClass {
    static constexpr int i = 42;
};

int main() {
    std::cout << MyClass::i << std::endl;
    // undefined reference to `MyClass::i'
    //std::cout << &MyClass::i << std::endl;
}

but you can't do things like taking its address, or else it becomes odr-used, see also: https://en.cppreference.com/w/cpp/language/static "Constant static members" and Defining constexpr static data members

Any way to fully inline it?

TODO: is there any way to fully inline the variable, without using any memory at all?

Much like what the preprocessor does.

This would require somehow:

  • forbidding or detecting if the address of the variable is taken
  • add that information to the ELF object files, and let LTO optimize it up

Related:

Tested in Ubuntu 18.10, GCC 8.2.0.

Stellite answered 22/12, 2018 at 19:31 Comment(3)
Thanks for this answer. It's to be noted that "a static member variable (but not a namespace-scope variable) declared constexpr is implicitly an inline variable. ".Chelsiechelsy
@Chelsiechelsy , yes, this is another possible workaround, I had mentioned that one at: #38043942 will add here as well.Stellite
inline has the problem that, if another DLL was linked against your DLL already, and you changed just that value than rebuilt your DLL, the old DLL would keep on using the old value. extern const solves this problem, but requires an actual memory lookup every time one of your link-clients queries the value.Tamper
M
5

You can use them together. But you need to be consistent on your use of const because when C++ does name decoration, const is included in the type information that is used to decorate the symbol names. so extern const int i will refer to a different variable than extern int i

Unless you use extern "C" {}. C name decoration doesn't pay attention to const.

Multivalent answered 3/2, 2010 at 9:39 Comment(0)
K
1

You can use them together and you can do all sorts of things which ignore the const keyword, because that's all it is; a keyword. It tells the compiler that you won't be changing a variable which in turn allows the compiler to do some useful optomisations and stops you from changing things you didn't mean to.

Possibility.com has a decent article with some more background.

Killough answered 3/2, 2010 at 9:38 Comment(0)
T
0

An additional note to this answer from https://en.cppreference.com/w/cpp/language/storage_duration

Names at the top-level namespace scope (file scope in C) that are const and not extern have external linkage in C, but internal linkage in C++.

Internal linkage means the name is only accessible within the current translation unit. You cannot use extern in another file to refer to names with internal linkage. To explicitly mark a const name with explicit linkage, use extern.

// foo.cpp (refers to bar from bar.cpp)
extern const bool bar;

// bar.cpp
extern const bool bar = true;
Treble answered 8/9, 2022 at 21:57 Comment(0)
C
-1

Yes, you can use them together.

If you declare "extern const int i", then i is const over its full scope. It is impossible to redefine it as non-const. Of course you can bypass the const flag by casting it away (using const_cast).

Calbert answered 3/2, 2010 at 9:33 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.