找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 2105|回复: 6

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

  [复制链接]
  • 打卡等级:常驻代表
  • 打卡总天数:34
  • 打卡月天数:6
  • 打卡总奖励:9027
  • 最近打卡:2025-12-17 23:15:51

2823

主题

541

回帖

2万

积分

管理员

积分
22569
发表于 2022-4-6 21:16:51 | 显示全部楼层 |阅读模式
问题描述
最近使用 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异常。
问题解决
知道了问题所在,解决起来就很简单了。一个异步执行的线程,除了全局、动态分配的内存等非局部的对象可共享内存数据进行读写,局部数据都要进行数据拷贝以实现隔离。基于上面的理论,这里应该是 UI 线程调用时传来的局部变量执行拷贝,动态申请的对象直接引用。由于实际执行的代码体中只使用了this指针和传来的函数参数,所以都执行拷贝即可。
在任务线程的 lambda 执行函数中,将捕捉引用改为捕捉值,内部的doWithSetRunning的 lambda 执行函数捕捉该异步线程捕捉值得到的拷贝的引用,即可实现非局部变量(this指向的内存对象)的共享与局部变量(_callback 回调函数对象)的隔离。
修改代码如下:

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();

}
最后,提醒大家一定要注意 lambda 的引用传递的正确性,因为小编已经遇到多次这里的问题,而在异步场景下就更要注意对象传递过程中各对象的传递关系与生命周期了。

本文来自博客园,作者:_哲思,转载请注明原文链接:https://www.cnblogs.com/zhe-si/p/16104721.html


来自圈子: 工控上位机学习
工控课堂 www.gkket.com
  • 打卡等级:即来则安
  • 打卡总天数:28
  • 打卡月天数:7
  • 打卡总奖励:7961
  • 最近打卡:2025-12-22 17:16:30

3317

主题

285

回帖

2万

积分

管理员

积分
24106
发表于 2022-4-8 09:48:14 | 显示全部楼层
强烈支持楼主ing……
工控课堂 www.gkket.com

0

主题

106

回帖

157

积分

新手上路

积分
157
发表于 2025-11-15 16:41:51 | 显示全部楼层
哈哈哈哈笑不活,楼主这脑洞绝了!
工控课堂 www.gkket.com

0

主题

101

回帖

266

积分

注册会员

积分
266
发表于 2025-11-15 17:16:22 | 显示全部楼层
评论区人才辈出,笑到停不下来😂
工控课堂 www.gkket.com

0

主题

93

回帖

243

积分

注册会员

积分
243
发表于 2025-11-15 23:35:57 | 显示全部楼层
学到干货了,感谢分享,已火速收藏
工控课堂 www.gkket.com

0

主题

94

回帖

138

积分

新手上路

积分
138
发表于 2025-11-17 05:48:51 | 显示全部楼层
同款经历!简直是世另我
工控课堂 www.gkket.com

0

主题

56

回帖

75

积分

新手上路

积分
75
发表于 2025-11-17 18:21:16 | 显示全部楼层
学到了学到了,这波分享太实用啦!
工控课堂 www.gkket.com
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

站长推荐上一条 /1 下一条

QQ|手机版|免责声明|本站介绍|工控课堂 ( 沪ICP备20008691号-1 )

GMT+8, 2025-12-22 20:13 , Processed in 0.087102 second(s), 26 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表