前言
編譯器在進行優(yōu)化的時候,可能為了效率而交換不相關(guān)的兩條相鄰指令的執(zhí)行順序。也就是指令重排,這也就引發(fā)了一些問題,下面就帶大家看兩個經(jīng)典的問題。
單例模式
第一個例子來自單例模式的雙加鎖,下面是典型的雙加鎖的單例模式代碼:
T* ptr = nullptr;
T* GetInstance() {
if (nullptr == ptr) {
lock();
if (nullptr == ptr) {
ptr = new T;
}
unlock();
}
return ptr;
}
上面的代碼看起來沒問題,并且采用了雙加鎖,能減少鎖的競爭。
我們知道 C++ 的 new 做了兩件事:
- 調(diào)用 ::operator new 分配內(nèi)存
- 調(diào)用構(gòu)造函數(shù)
所以 ptr = new T
包含了三個步驟:
- 調(diào)用 ::operator new 分配內(nèi)存
- 在內(nèi)存的位置上調(diào)用構(gòu)造函數(shù)
- 將內(nèi)存的地址賦值給 ptr
在這三步中,2 和 3 的順序是可以交換的。也就是說,有可能:有一個線程分配了內(nèi)存并將地址賦值給 ptr 了,但還沒有初始化該內(nèi)存。另一線程檢測 ptr 不為空,就直接拿去使用了,這時可能引起不可預料的結(jié)果。
通常情況下,可以調(diào)用 CPU 提供的一條指令來解決該情況,這指令被稱為 barrier。一條 barrier 指令會阻止 CPU 將該指令交換到 barrier 之后,也不能將之后的指令交換到 barrier 之前。
#define barrier() __asm__ volaticle("lwsync")
T* ptr = nullptr;
T* GetInstance() {
if (nullptr == ptr) {
lock();
if (nullptr == ptr) {
T* tmp = new T;
barrier();
ptr = tmp;
}
unlock();
}
return ptr;
}
智能指針
有時我們會用智能指針來管理內(nèi)存,防止我們忘記釋放,或在某些場景手動釋放十分麻煩。
我們有如下代碼:
processQgw(shared_ptr<Qgw>(new Qgw), test());
令人遺憾的是,上述代碼仍可能產(chǎn)生內(nèi)存泄漏,并難以察覺。
編譯器在調(diào)用函數(shù)前,需要準備好參數(shù),所以上面代碼會做以下三件事:
- 調(diào)用 test()
- 執(zhí)行 new Qgw
- 調(diào)用 shared_ptr 構(gòu)造函數(shù)
C++ 編譯器會以什么次序完成這些事情呢?可以確定的是 new Qgw 一定在 調(diào)用 shared_ptr 之前完成,上述三件事也一定在調(diào)用 processQgw 之前完成。編譯器可能以下列次序調(diào)用:
- 執(zhí)行 new Qgw
- 調(diào)用 test
- 調(diào)用 shared_ptr 構(gòu)造函數(shù)
如果 test 的調(diào)用導致異常,new Qgw 返回的指針就再也找不到了,也就引起了內(nèi)存泄漏。因為我們還沒將返回的指針置入智能指針,智能指針也就對這種情況無能為力了。
為避免這類問題:使用分離語句,單獨寫出創(chuàng)建 Qgw,將它置入一個智能指針內(nèi),然后再把智能指針傳給 processQgw:文章來源:http://www.zghlxwxcb.cn/news/detail-420541.html
shared_ptr<Qgw> pq = new Qgw;
processQgw(pq, test());
上述解決方法之所以能行,是因為編譯器對于「跨越語句的各項操作」沒有重新排列的自由,只有在一條語句內(nèi)它才擁有那個自由。文章來源地址http://www.zghlxwxcb.cn/news/detail-420541.html
到了這里,關(guān)于編譯器的過度優(yōu)化的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!