![](/img/trans.png)
[英]Benefit of declaring a local variable const in C++, if its value is not known during compile time
[英]Why don't c++ compilers replace this access to a const class member with its value known at compile time?
在這個代碼片段中,為什么 c++ 編譯器在編譯test()
時不只返回 1,而是從 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;
}
編譯器 output:
test(Test const&): # @test(Test const&)
mov eax, dword ptr [rdi]
ret
我本來期望:
test(): # @test(Test const&)
mov eax, 1
ret
是否有任何符合標准的方法來修改Test::x
的值以包含與1
不同的值? 還是允許編譯器進行此優化,但 gcc 和 clang 都沒有實現它?
編輯:當然,您立即發現我的錯誤是將此作為最小示例,即允許對結構進行聚合初始化。 我用一個空的默認構造函數更新了代碼,以防止這種情況發生。 ( godbolt 上的舊代碼)
我相信這是因為您仍然可以使用其他 x 值構造一個 Test 實例,並使用如下初始化器列表:
Test x{2};
cout << test(x);
現在,您已不允許使用構造函數創建具有不同x
值的Test
object 實例,但 gcc/clang 仍未優化。
使用char*
或memcpy
創建具有不同x
值的Test
object 的對象表示可能是合法的。 這將使優化非法。
例如,
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));
唯一有問題的步驟是最后的memcpy
回到foo
; 這就是創建帶有構造函數無法生成的x
值的Test
object 的原因。 在 C++ 的引用上下文中, const
表示您不能通過此引用修改此 object。 我不知道這如何適用於非const
object 的const
成員。
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
它可能是也可能不是錯過的優化。 許多錯過的優化是編譯器不尋找的東西,因為它的計算成本很高(即使是提前編譯器也不能在潛在的大函數上粗心地使用指數時間算法)。
不過,這似乎並不太昂貴,只需檢查構造函數默認值是否無法被覆蓋。 盡管在制作更快/更小的代碼方面它的價值似乎很低,因為希望大多數代碼不會這樣做。
這肯定是編寫代碼的次優方式,因為您在保持此常量的 class 的每個實例中都在浪費空間。 所以希望它不會經常出現在真正的代碼庫中。 static constexpr
是慣用的,並且比const
per-instance 成員 object 好得多,如果您有意使用每個類常量。
然而,恆定傳播可能非常有價值,因此即使它很少發生,它也可以在它確實發生的情況下開啟重大優化。
要進行此優化,您需要告訴編譯器x
不能更改:
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
在您的情況下,這意味着您有一個不可修改的變量,如果沒有通過任何其他方法給出,它將被設置為給定值。 但至少還有其他兩種方法,例如:
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;
}
如果你想說:我的類型總是有相同的常數,你只需寫static constexpr int x = 1;
這是一個完全不同的語義。 如果你這樣做,組裝將是你所期望的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.