In this code snippet, why don't c compilers just return 1 when compiling test()
, but read the value from memory?
struct Test {
const int x = 1;
// Do not allow initializing x with a different value
Test() {}
};
int test(const Test& t) {
return t.x;
}
Compiler output:
test(Test const&): # @test(Test const&)
mov eax, dword ptr [rdi]
ret
I would have expected:
test(): # @test(Test const&)
mov eax, 1
ret
Is there any standard-compliant way to modify the value of Test::x
to contain a different value than 1
? Or would the compilers be allowed to do this optimization, but neither gcc nor clang have implemented it?
EDIT: Of course you immediately found my mistake in making this a minimum example, that is allowing aggregate initialization for the struct. I updated the code with an empty default constructor that prevents that. (Old code on godbolt)
CodePudding user response:
I believe it's because you can still construct an instance of Test with other values of x with an initializer list like this:
Test x{2};
cout << test(x);
Demo: https://www.ideone.com/7vlCmX
CodePudding user response:
In your case it means that you have a non modifiable variable which will be set to a given value if not given by any other method. But there are at minimum two other methods like:
struct X {
const int y = 1;
};
int test(const X& t) {
return t.y;
}
struct Y: public X
{
Y():X{9}{}
};
int main()
{
X x1{3};
std::cout << test(x1) << std::endl;
Y y1{};
std::cout << test(y1) << std::endl;
}
If you want to say: My type always have the same constant, you simply should write static constexpr int x = 1;
which is a totally different semantic. And if you do that, the assembly will be what you expected.
CodePudding user response:
To make this optimization happen you need to tell compiler that x
cannot have different value in any case:
struct Test {
constexpr
static int x = 1;
};
int test(const Test& t) {
return t.x;
}
test(Test const&): # @test(Test const&)
mov eax, 1
ret
CodePudding user response:
Now you've disallowed using a constructor to create an instance of a Test
object with a different x
value, but gcc/clang still aren't optimizing.
It may be legal to use char*
or memcpy
to create an object-representation of a Test
object with a different x
value. That would make the optimization illegal.
For example,
Test foo;
char buf[sizeof(foo)];
memcpy(buf, &foo, sizeof(foo));
buf[0] = 3; // on a little-endian system like x86, this is buf.x = 3; - the upper bytes stay 0
memcpy(&foo, buf, sizeof(foo));
The only questionable step is the final memcpy
back into foo
; this is what creates a Test
object with an x
value the constructor couldn't produce. In reference contexts in C , const
means you can't modify this object through this reference. I don't know how that applies for a const
member of a non-const
object.
We can see from this example that GCC and clang leave room for non-inline function calls to modify that member of an already-constructed Test
object:
void ext(void*); // might do anything to the pointed-to memory
int test() {
Test foo; // construct with x=1
ext (&foo);
return foo.x;
}
# GCC11.2 -O3. clang is basically equivalent.
test():
sub rsp, 24 # stack alignment wasted 16 bytes
lea rdi, [rsp 12]
mov DWORD PTR [rsp 12], 1 # construct with x=1
call ext(void*)
mov eax, DWORD PTR [rsp 12] # reload from memory, not mov eax, 1
add rsp, 24
ret
It may or may not be a missed optimization. Many missed-optimizations are things compilers don't look for because it would be computationally expensive (even an ahead-of-time compiler can't use exponential-time algorithms carelessly on potentially-large functions).
This doesn't seem too expensive to look for, though, just checking if a constructor default has no way to be overridden. Although it seems lowish in value in terms of making faster / smaller code since hopefully most code won't do this.
It's certainly a sub-optimal way to write code, because you're wasting space in each instance of the class holding this constant. So hopefully it doesn't appear often in real code-bases. static constexpr
is idiomatic and much better than a const
per-instance member object if you intentionally have a per-class constant.
However, constant-propagation can be very valuable, so even if it only happens rarely, it can open up major optimizations in the cases it does.