問題描述
在項目某次開發(fā)中,測試過程中出現(xiàn)了coredump問題。經(jīng)過asan工具檢測,報了heap-use-after-free內(nèi)存錯誤,最終定位到竟是無意中添加了一個引用&導(dǎo)致的!
開發(fā)時因為看到相關(guān)類訪問類成員的接口函數(shù)未返回引用,而是返回了一個拷貝,因此想著要將返回值改為引用,避免多余的拷貝。例如在以下代碼中,我們將GetArr函數(shù)的返回值改為引用:
class Foo {
private:
std::vector<int> arr;
public:
std::vector<int>& GetArr() { // add & for return type
return arr;
}
// other functions
};
看起來這并沒什么問題,但是在多線程環(huán)境下,加一個&可能就會導(dǎo)致程序崩潰。
問題分析
比如以下代碼展示了一個簡單的例子,表面看起來GetArr是有鎖保護(hù)的,但實際上鎖保護(hù)被打破了:
class Foo {
private:
std::vector<int> arr;
std::mutex mut;
public:
std::vector<int>& GetArr() {
std::lock_gurad<std::mutex> lk(mut);
return arr;
}
// other functions
};
為什么呢?GetArr返回一個 vector 的引用,然后立即釋放鎖。因此,返回的引用可以在沒有任何保護(hù)的情況下被修改。當(dāng)多個線程對同一個Foo實例的arr進(jìn)行讀寫時,就會導(dǎo)致數(shù)據(jù)競爭,從而導(dǎo)致程序崩潰的風(fēng)險。
如何避免?
對于類對象的多線程共享數(shù)據(jù),接口應(yīng)返回拷貝而不是引用。
std::vector<int> GetArr() { // return a copy
return arr;
}
總結(jié)
再翻翻《C++并發(fā)實戰(zhàn)》,才發(fā)現(xiàn)書中早已經(jīng)總結(jié)了這個陷阱:
通常來說,類的所有成員函數(shù)在訪問任何數(shù)據(jù)成員之前,假如都先對互斥加鎖,并在完成訪問后解鎖,共享數(shù)據(jù)就可很好地受到全方位保護(hù)??上屡c愿違:如果類的成員函數(shù)返回指向共享數(shù)據(jù)的指針或引用,那么這些數(shù)據(jù)就會在鎖外被訪問,從而破壞了互斥的效果。
因此,我們可以總結(jié)一個簡單的規(guī)則:不得向鎖所在的作用域之外傳遞指針和引用,指向受保護(hù)的共享數(shù)據(jù),無論是通過函數(shù)返回值將它們保存到對外可見的內(nèi)存,還是將它們作為參數(shù)傳遞給使用者提供的函數(shù)。
參考
- C++ Concurrency in Action, Anthony Williams
你好,我是七昂,致力于分享C++、計算機(jī)底層、機(jī)器學(xué)習(xí)等系列知識。希望我們能一起探索程序員修煉之道。如果我的創(chuàng)作內(nèi)容對您有幫助,請點贊關(guān)注。如果有問題,歡迎隨時與我交流。感謝你的閱讀。