编程语言
首页 > 编程语言> > c++异步回调函数引用传递空指针异常

c++异步回调函数引用传递空指针异常

作者:互联网

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),网上对其的准确描述很少,于是进行总结:

问题分析

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 的引用传递的正确性,因为小编已经遇到多次这里的问题,而在异步场景下就更要注意对象传递过程中各对象的传递关系与生命周期了。

标签:std,异步,函数,错误代码,代码,c++,线程,initProTestCasesEnvAsync,指针
来源: https://www.cnblogs.com/zhe-si/p/16104721.html