c++異步回呼函式參考傳遞空指標例外
問題描述
最近使用 c++ / qt 開發的一個桌面應用,運行到一處異步執行python腳本任務的方法處報錯:
行程已結束,退出代碼-1073741819 (0xC0000005)
此處是單獨開一個執行緒異步執行一個python腳本后,回呼 UI 執行緒傳來的回呼函式將結果回傳給 UI 執行緒,大致代碼如下:
void TestCaseProject::initProTestCasesEnvAsync(const std::function<void(std::vector<std::pair<std::string, Json::Value>>)>& _callback) {
std::thread t{[&] () {
doWithSetRunning([&]() {
auto result = this->initProTestCasesEnv();
_callback(result);
});
}};
t.detach();
}
void callBackFunc(const std::vector<std::pair<std::string, Json::Value>>& rps) {
// do some things
}
// UI執行緒某函式呼叫initProTestCasesEnvAsync
void uiFunc() {
// create project and other code
project->initProTestCasesEnvAsync(callBackFunc);
// do other things
}
解決方案
問題解讀
搜索行程已結束,退出代碼-1073741819 (0xC0000005),網上對其的準確描述很少,于是進行總結:
-
退出代碼就是執行的程式退出時的回傳值,如
main函式直接回傳、呼叫程式退出的函式void exit(int _Code)、有未解決的例外從程式拋出到系統后回傳系統定義的錯誤退出碼等,通常是一個十六進制 int 值, -
退出代碼中括號內的才是實際的十六進制退出代碼(一般使用這個),前面是其十進制表示(因為起始有一個十六進制數 c 所以變成負數,類似一個標識,用來區分各系列錯誤代碼),
-
錯誤代碼無法確定錯誤的詳細資訊,只能大致進行判斷,具體情況需要進一步分析代碼背景關系,或者捕捉例外、進行除錯來確定,
-
錯誤退出代碼一般由未處理的例外觸發,而不是直接退出程式并回傳該代碼,
-
在Windows系統下,錯誤代碼定義在頭檔案
<winerror.h>
Windows錯誤代碼詳情可見官方檔案說明,同時,微軟官方提供了一個錯誤代碼的含義查找工具,下載鏈接,
-
舉一反三,在其他作業系統上也有定義錯誤代碼的位置,但定義位置可能不同,大家可以自行查找,不過,錯誤代碼及其含義在各系統平臺定義基本是一致的,不會有太大出入,
問題分析
1. 錯誤代碼分析
使用微軟錯誤代碼查找工具查找錯誤代碼0xC0000005,結果如下:
PS D:\tools> .\Err_6.4.5.exe C0000005
# for hex 0xc0000005 / decimal -1073741819
ISCSI_ERR_SETUP_NETWORK_NODE iscsilog.h
# Failed to setup initiator portal. Error status is given in
# the dump data.
STATUS_ACCESS_VIOLATION ntstatus.h
# The instruction at 0x%p referenced memory at 0x%p. The
# memory could not be %s.
USBD_STATUS_DEV_NOT_RESPONDING usb.h
# as an HRESULT: Severity: FAILURE (1), FACILITY_NONE (0x0), Code 0x5
# for hex 0x5 / decimal 5
WINBIO_FP_TOO_FAST winbio_err.h
# Move your finger more slowly on the fingerprint reader.
# as an HRESULT: Severity: FAILURE (1), FACILITY_NULL (0x0), Code 0x5
ERROR_ACCESS_DENIED winerror.h
# Access is denied.
# 5 matches found for "C0000005"
經過分析,其中,第5條查找結果(第20行)就是問題的主要原因(主要看定義在<winerror.h>中的代碼),
ERROR_ACCESS_DENIED:Access is denied.表示訪問被拒絕,這是訪問了無權訪問的記憶體地址空間,常見的場景有:
- 空指標
- 陣列越界
- 釋放記憶體后產生的野指標
以上場景都會造成未定義行為,并可能拋出例外觸發ERROR_ACCESS_DENIED錯誤并退出,
2. 代碼除錯
在除錯模式運行,查看拋出的例外資訊如下:
terminate called after throwing an instance of 'std::bad_function_call'
what(): bad_function_call
例外std::bad_function_call在呼叫空的函式物件(std::function)時拋出,空的函式物件一般情況是未給函式物件賦值或賦值null,
我們回到問題描述的代碼部分,回呼函式的函式物件是 UI 主執行緒中某個函式將全域函式的指標傳入構造的,initProTestCasesEnvAsync方法的引數是常量參考,被執行緒執行的 lambda 函式捕捉其參考,又被執行緒執行函式內的doWithSetRunning的 lambda 函式引數捕捉其參考,并在其內呼叫該函式物件,
經過單行除錯,發現例外就是在異步執行緒執行該回呼函式物件是觸發的,
機智的小伙伴可能已經發現,根據上面描述的變數傳遞關系,最終執行的回呼函式物件就是 UI 執行緒呼叫initProTestCasesEnvAsync時傳入callBackFunc函式指標并構建的區域函式物件的參考,正常一個串行執行的程式,這樣自然沒有問題,在initProTestCasesEnvAsync回傳時已完成callBackFunc的呼叫,但若創建回呼函式物件與執行該回呼函式物件處在不同執行緒,就會發生區域的回呼函式物件因為其背景關系的函式異步執行結束而釋放記憶體,導致執行執行緒保存的回呼函式的參考內部空指標,呼叫時觸發std::bad_function_call例外,
問題解決
知道了問題所在,解決起來就很簡單了,在doWithSetRunning的 labmda 函式引數中,將 _callback 改為基于值的捕捉,使其在此處復制 _callback 函式物件,修改代碼如下:
void TestCaseProject::initProTestCasesEnvAsync(const std::function<void(std::vector<std::pair<std::string, Json::Value>>)>& _callback) {
std::thread t{[&] () {
doWithSetRunning([&, _callback]() {
auto result = this->initProTestCasesEnv();
_callback(result);
});
}};
t.detach();
}
最后,提醒大家一定要注意 lambda 的參考傳遞的正確性,因為小編已經遇到多次這里的問題,而在異步場景下就更要注意物件傳遞程序中各物件的傳遞關系與生命周期了,
本文來自博客園,作者:_哲思,轉載請注明原文鏈接:https://www.cnblogs.com/zhe-si/p/16104721.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/456147.html
標籤:其他
