在這段代碼片段中,為什么 c 編譯器在編譯時不只回傳 1 test(),而是從記憶體中讀取值?
struct Test {
const int x = 1;
// Do not allow initializing x with a different value
Test() {}
};
int test(const Test& t) {
return t.x;
}
goolbolt 上的代碼
編譯器輸出:
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 上的舊代碼)
uj5u.com熱心網友回復:
我相信這是因為您仍然可以使用 x 的其他值和這樣的初始值設定項串列構造一個 Test 實體:
Test x{2};
cout << test(x);
演示:https : //www.ideone.com/7vlCmX
uj5u.com熱心網友回復:
在您的情況下,這意味著您有一個不可修改的變數,如果沒有通過任何其他方法給出,它將被設定為給定的值。但至少還有其他兩種方法,例如:
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;一個完全不同的語意。如果你這樣做,大會將是你所期望的。
添加: 更改代碼后,我們仍然沒有看到對小測驗功能的優化。而在對面彼得斯想法,memcpy的在現有的物件是有效的,我相信這是UB,我看沒有爭論下去,該功能不能被優化。
但是:我們有一個函式,它可能需要更多的東西,我們應該看看完整的背景關系。編譯器有多個優化步驟。也就是說,在這種情況下,不僅是常量傳播,還有行內。如果我們進行更真實的編碼,我們會看到代碼在被行內后會得到全面優化!聯結器將從可執行檔案中洗掉您的函式,因為它不會被使用。我們可以相信,兩條匯編指令的功能總是行內的,因為回傳值的呼叫和移動總是更廣泛。結果是我們所期望的,常量在內聯發生后傳播。
我們還應該問,為什么程式員要撰寫一個永遠無法更改的單個(非靜態)常量成員。這將毫無意義地浪費每個單獨物件的存盤空間。為此,我們有constexpr static. 代碼的壞處不是缺少常量傳播,正如我們在查看現實世界代碼時看到的那樣,稍后會發生這種情況,而是浪費的記憶體,如果我們真的生成物件,我們在給定的代碼示例中沒有. 我不確定是否允許編譯器從永遠不會真正使用的物件中洗掉資料。
簡而言之,編譯器會優化所有內容,即使在任何時候都不會創建物件!結果只有:
main: # @main
mov eax, 1
ret
查看完整的優化代碼
uj5u.com熱心網友回復:
為了實作這種優化,你需要告訴編譯器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
uj5u.com熱心網友回復:
現在您已經禁止使用建構式創建具有Test不同x值的物件實體,但是 gcc/clang 仍然沒有優化。
使用char*或memcpy創建具有Test不同x值的物件的物件表示可能是合法的。這將使優化非法。
例如,
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;這就是Test使用x建構式無法生成的值創建物件的原因。在 C 的參考背景關系中,const意味著您不能通過此參考修改此物件。我不知道這如何適用于非物件的const 成員const。
我們可以從這個例子中看到 GCC 和 clang 為非行內函式呼叫留出了空間來修改已經構造的Test物件的成員:
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
它可能是也可能不是錯過的優化。許多遺漏的優化是編譯器不會尋找的東西,因為它的計算成本很高(即使是提前編譯器也不能在潛在的大型函式上粗心地使用指數時間演算法)。
但是,這看起來并不太昂貴,只是檢查建構式默認值是否無法被覆寫。盡管在制作更快/更小代碼方面的價值似乎很低,因為希望大多數代碼不會這樣做。
這當然是撰寫代碼的次優方式,因為您在持有此常量的類的每個實體中都在浪費空間。所以希望它不會經常出現在真實的代碼庫中。 如果您有意擁有每個類的常量,那么它static constexpr是慣用的,并且比每個實體的成員物件要好得多const。
然而,常量傳播可能非常有價值,所以即使它很少發生,它也可以在它發生的情況下開啟主要的優化。
轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/397882.html
上一篇:使用包含模擬介面的unique_ptr使用依賴項注入時的記憶體泄漏警告
下一篇:帶有遞回數C 的直角三角形模式
