I'm delving into address constant expressions while reading "C++ Programming Language 4th edition" book. It has a short paragraph which describes address constant expressions:
The address of a statically allocated object, such as a global
variable, is a constant. However, its value is assigned by the linker,
rather than the compiler, so the compiler cannot know the value of
such an address constant. That limits the range of constant
expressions of pointer and reference type. For example:
constexpr const char* p1 = "asdf";
constexpr const char* p2 = p1; //OK
constexpr const char* p2 = p1+2; //error: the compiler does not know the value of p1
constexpr char c = p1[2]; //OK, c=='d'; the compiler knows the value pointed to by p1
I have two questions.
This one is rather trivial - since the compiler doesn't know that address of a static object, then how can it evaluate the second statement during compile time? After all, the fact that the compiler doesn't know the value of p1+2
, implies that p1
has to be unknown in the first place, right? g++ 4.8.1 with all the rigorous flags turned on accepts all these statements, though.
As exemplified in this topic:
static constexpr int N = 3;
int main()
{
constexpr const int *NP = &N;
return 0;
}
Here, NP is declared as an address constant-expression, i.e. an
pointer that is itself a constant expression. (This is possible when
the address is generated by applying the address operator to a
static/global constant expression.)
This would also work if we declared N
as simply const
without constexpr
. However, p1
has to be declared explicitly using constexpr
in order to p2
be a valid statement. Otherwise we get:
error: the value of ‘p1’ is not usable in a constant expression
Why is that? "asdf"
is of const char[]
as far as I know.
N3485 contains about "address constant expression"
An address constant expression is a prvalue core constant expression (after conversions as required by the context) of ... pointer type that evaluates to the address of an object with static storage duration ....
The third character object of a string literal is such an object (see the elaborations on 2.14.5), not any less than the first one of it.
Note that there is no use of variable, but object here (so we are allowed to access array elements aswell as class members to get an address constant expression, provided that the array or class object has static storage duration and the access does not otherwise violate the rules of core constant expressions).
Technically there is a relocation in the object file that the linker will carry out:
constexpr const char *x = "hello";
extern constexpr const char *y = x + 2;
We will compile this down to an object file and look what it does
[js@HOST1 cpp]$ clang++ -std=c++11 -c clangtest.cpp
[js@HOST1 cpp]$ objdump --reloc ./clangtest.o
./clangtest.o: file format elf32-i386
RELOCATION RECORDS FOR [.rodata]:
OFFSET TYPE VALUE
00000000 R_386_32 .L.str
[js@HOST1 cpp]$ objdump -s -j .rodata ./clangtest.o
./clangtest.o: file format elf32-i386
Contents of section .rodata:
0000 02000000 ....
[js@HOST1 cpp]$
The linker will take the value what is already in the section, and add it to the value of the symbol (by which is meant its address in the symbol table) referenced by the "VALUE" property of the relocation (in our case we added 2
, so Clang/LLVM hardcoded a 2
in the section).
However, p1 has to be declared explicitly using constexpr in order to p2 be a valid statement.
That is because you are relying on its value, rather than its adress, to be constant. In general (see below) you must previously mark it as constexpr so that the compiler at that point can validate that any later read access can definitely rely on getting a constant. You may want to change it as follows and see it working (I THINK since there is a special case for initialized const objects of integral and enumeration types you can even read from the below p1
array in a constexpr context, even without it being marked constexpr
. However my clang seems to reject it)
const char p1[] = "asdf";
constexpr const char *x = p1 + 2; // OK!
constexpr char y = p1[2]; // OK!