IMHO, Peter provided the explanation in his comment:
If a pointer is initialised to contain the address of a variable, and that pointer can be accessed from another compilation unit, then it would be reasonable for the compiler to allow for the possibility that the pointer IS dereferenced after being initialised in some compilation unit that is not visible to the compiler. One consequence of that is not optimising the pointer or the variable out of existence. There are numerous other reasoning approaches that might lead to the same outcome, depending on what code the compiler can actually see.
and this is exactly what I think too.
The const
in C++ is a little bit confusing. It looks like the abbreviation of “constant” but actually it means “read-only”.
This in mind, I never wondered why the following code is legal in C:
enum { N = 3 };
static int a[N]; /* legal in C: N is a constant. */
but this not:
const int n = 3;
static int b[n]; /* illegal in C: n is a read-only variable */
When I switched to C++, I assumed the above for C++ until I realized in a discussion with a colleague that I was wrong. (Not that this broke any written code of mine, but I hate it to be wrong.) ;-)
const int n = 3;
static int b[n]; // legal in C++
and Const propagation is the technique which makes it legal.
However, even with const propagation const int x;
is still a read-only variable which might be addressed.
The OP provided a link about this topic (which might explain it even better than above):
SO: why the size of array as a constant variable is not allowed in C but allowed in C++?
To make this a ful-featured answer I tried to prepare a sample which illustrates the differences:
#include <iostream>
const int x1 = 1;
static const int x1s = 11;
extern const int x1e = 12;
const int x2 = 2;
extern const int *const pX2 = &x2;
const int x3 = 3;
static const int *const pX3 = &x3;
int main()
{
// make usage of values (to have a side-effect)
std::cout << x1;
std::cout << x1s;
std::cout << x1e;
std::cout << x2;
std::cout << pX2;
std::cout << x3;
std::cout << pX3;
// done
return 0;
}
and the outcome of gcc 8.2
with -O3
:
; int main()
main:
; {
sub rsp, 8
; // make usage of values (to have a side-effect)
; std::cout << x1;
mov esi, 1
mov edi, OFFSET FLAT:_ZSt4cout
call _ZNSolsEi
; std::cout << x1s;
mov esi, 11
mov edi, OFFSET FLAT:_ZSt4cout
call _ZNSolsEi
; std::cout << x1e;
mov esi, 12
mov edi, OFFSET FLAT:_ZSt4cout
call _ZNSolsEi
; std::cout << x2;
mov esi, 2
mov edi, OFFSET FLAT:_ZSt4cout
call _ZNSolsEi
; std::cout << pX2;
mov esi, OFFSET FLAT:_ZL2x2
mov edi, OFFSET FLAT:_ZSt4cout
call _ZNSo9_M_insertIPKvEERSoT_
; std::cout << x3;
mov esi, 3
mov edi, OFFSET FLAT:_ZSt4cout
call _ZNSolsEi
; std::cout << pX3;
mov esi, OFFSET FLAT:_ZL2x3
mov edi, OFFSET FLAT:_ZSt4cout
call _ZNSo9_M_insertIPKvEERSoT_
; // done
; return 0;
; }
xor eax, eax
add rsp, 8
ret
and the IMHO most interesting part – the global variables:
; const int x3 = 3;
_ZL2x3:
.long 3
; extern const int *const pX2 = &x2;
pX2:
.quad _ZL2x2
; const int x2 = 2;
_ZL2x2:
.long 2
; extern const int x1e = 12;
x1e:
.long 12
x1
, x1s
, and pX3
have been optimized away because they are const
and not remarked for external linkage.
x1e
and pX2
have been allocated because they are remarked for external linkage.
x2
has been allocated because it is referred by pX2
which is remarked for external linkage. (Something from extern may access x2
via pX2
.)
x3
was the most tricky one for me. pX3
has been used (in std::cout << pX3;
). Although, its value itself is inlined it refers to x3
. Furthermore, although the access to x3
(in std::cout << x3;
) was inlined as well, the usage of the pointer pX3
initialized with &x3
prevented to optimize this storage away.
Live Demo on godbolt (which has a nice colored dual-view to make it easy to explore)
I did the same with clang 7.0.0
and the outcome was similar.
(I tried it also with msvc v19.15
but I was not able (not patient enough) to evaluate the outcome.)
Concerning 4., I tried additionally:
const int x4 = 4;
static const int *const pX4 = &x4;
and added to main()
:
std::cout << x4;
// No: std::cout << pX4;
In this case, there was no storage allocated – neither for x4
nor for pX4
. (pX4
was optimized away, leaving no “reference” to x4
which in turn was optimized away as well.)
It's amazing...