【C++】函数直接返回bool值和返回bool变量差异

news2025/4/15 13:32:30

函数直接返回bool值和返回bool变量差异

背景

在工作中遇到一个比较诡异的问题,场景是给业务方提供的SDK有一个获取状态的函数GetStatus,函数的返回值类型是bool,在测试过程中发现,SDK返回的是false,但是业务方拿到的返回值是true。SDK是C语言和C++语言编写的,C语言编写接口层, C++语言编写实际逻辑,业务方是unity,使用C#语言,通过DllImport引用SDK DLL

  • DllImport声明如下
[DllImport("SDK.dll", EntryPoint="GetStatus", CharSet=CharSet.Ansi, 
CallingConventin=CallingConvention.Cdec1)]
public static extern bool GetStatus([MarshalAs(UnmanagedType.LPStr)] string key);
  • C语言接口层
// 声明
__declspec(dllexport) bool GetStatus(const char* key); 
// 定义
bool GetStatus(const char *key)
{
	return cpp_instance->GetStatus(key);
}
  • C++实现
    C++ 将key对应的状态保存到一个map中,key类型为std::string,值为std::any
template<class typename T>
T GetStatus(const std::string &key)
{
	if(!status_map_.count(key)) {
		return {};
	}
	const auto &value = status_map_.at(key);
	if(!value.has_value()) {
		return {};
	}
	try {
		return std::any_cast<T>(value);
	} catch (const std::exception &e) {
		return {};
	}
}

排查

C++侧排查

通过 SDK的 unity demo 调试未能复现问题,将SDK Attach到业务进程调试问题复现,由于代码中没有中间变量保留结果,都是直接将结果返回。调试不方便。在代码中增加了一行日志打印。结果问题不复现了。

  • 增加一行日志后的代码
bool GetStatus(const char *key)
{
   auto ret = cpp_instance->GetStatus(key);
   std::cout << "get status result " << ret << std::endl;
   return ret;
}

百思不得其解,决定反汇编调试看下,看下增加日志前和增加日志后的汇编代码。
为了方便调试和说明,这里编写了复现的简单demo,如下:

#include <iostream>

typedef int (*GetBoolFuncPtr)();


static bool GetBool1()
{
    return {};
}
static bool GetBool2()
{
  /*  bool ret = GetBool1();
    std::cout << "ret:" << ret << std::endl;
    return ret;*/
    return GetBool1();
}

int main()
{

    /*GetBoolFuncPtr booll = reinterpret_cast<int (*)()>(GetBool1);
    GetBoolFuncPtr bool2 = reinterpret_cast<int (*)()>(GetBool2);
    std::cout << booll() << " " << bool2() << std::endl;*/
	std::cout << GetBool2() << std::endl;
    int input;
    std::cin >> input;
    return 0;
}
未增加日志打印的反汇编代码如下

未增加日志的反汇编代码
可以看到GetBool2()函数中直接返回了GetBool1()函数的结果,return {}的反汇编代码为xor al,alal表示RAX寄存的低8位, 而函数的返回值就是保存在RAX寄存中,所以对于返回值是boolC++函数,直接return {}是将RAX寄存器的低8位清零,RAX的其他位数是残值。之所以只清零低8位是因为在C++中bool 占1个字节。

增加日志的输出代码
static bool GetBool2()
{
    bool ret = GetBool1();
    std::cout << "ret:" << ret << std::endl;
    return ret;
}

反汇编代码
增加日志的反汇编代码
可以看到GetBool2()函数中通过一个中间变量ret保存了GetBool1()的返回值,并且打印ret的值,然后将ret返回,通过反汇编代码可以看到move byte ptr [ret], alGetBool1()的保存在al的值保存在了ret指向的地址,return ret对应的反汇编代码movezx eax, byte ptr [ret]ret的值保存到 eax寄存器,eax寄存器是RAX寄存器的低32位。这里的重点是movezx指令,movezx指令可以将较小的值用0扩展到较大的值。所以这里eax的高24位被清零。

C++侧排查总结
  • return {}返回bool值将清零RAX寄存器的低8位
  • return ret 返回bool值将清零RAX寄存器的低32位
    可以看到 增加了一行日志和没有这行日志的差别在于会清零返回值寄存器RAX的多少位。这个差别为什么会导致unity 拿到的结果不一样呢?需要继续排查。
unity排查
现状
  • 业务unity应用出现问题

  • SDK unity demo正常
    通过和业务开发沟通发现,业务unity应用后端使用的是il2cpp模式,而SDK unity demo则使用的是mono模式,也许是这里出现了问题,果然将unity demo的后端改成il2cpp模式后复现问题。

    那为什么在il2cpp模式下有问题呢?

    il2cpp模式会将C#代码转换成cpp代码,业务同学说在C#的DllImport地方增加解决了

    [return: MarshalAs(UnmanagedType.I1)]
    

    为什么增加这行代码就可以了,原来在C#中的UnmanagedType类型中bool变量是占4个字节,而使用il2cpp模式后C#的代码会被转换成C++代码。Dllimport的代码会转换成类似下面的逻辑(这里只摘出了重要代码)

    typedef  int32_t (CDECL * PInvokeFunc)(char*);
    static PInvokeFunc  il2cppPInvokeFunc;
    if(il2cppPInvokeFunc == NULL) {
    	il2cppPInvokeFunc  = il2cpp_codegen_resolve_pinvoke<PInvokeFunc>(IL2CPP_NATIVE_STRING("SDK.dll"), "GetStatus", ....);
    }
    
结论

从转换后的代码可以看到,il2cpp C++代码解析的GetStatus函数的返回值是int32_t,也就是说SDK内部返回bool的函数,这里被解析成int32_t了,返回int32_t会从返回值寄存器RAX中读取低32位,结合前面的C++demo分析可以解释了为什么没有打印日志拿到的返回值大概率是true,因为没有打印日志,返回 false只清零了RAX寄存器的低8位,而il2cpp中读取了32位,RAX寄存器大概率有其他函数调用的残值,导致il2cpp中读取到的值大概率为true。而打印了日志,返回false清零RAX寄存器的低32位, il2cpp代码读取正确

参考

https://stackoverflow.com/questions/20035826/why-dllimport-for-c-bool-as-unmanagedtype-i1-throws-but-as-byte-it-works
https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedtype?view=net-9.0

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2334212.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第1节:计算机视觉发展简史

计算机视觉与图像分类概述&#xff1a;计算机视觉发展简史 计算机视觉&#xff08;Computer Vision&#xff09;作为人工智能领域的重要分支&#xff0c;是一门研究如何使机器"看"的科学&#xff0c;更具体地说&#xff0c;是指用摄影机和计算机代替人眼对目标进行识…

英伟达Llama-3.1-Nemotron-Ultra-253B-v1语言模型论文快读:FFN Fusion

FFN Fusion: Rethinking Sequential Computation in Large Language Models 代表模型&#xff1a;Llama-3.1-Nemotron-Ultra-253B-v1 1. 摘要 本文介绍了一种名为 FFN Fusion 的架构优化技术&#xff0c;旨在通过识别和利用自然并行化机会来减少大型语言模型&#xff08;LLM…

云曦月末断网考核复现

Web 先看一个BUUCTF中的文件一个上传题 [BUUCTF] 2020新生赛 Upload 打开后是一个文件上传页面 随便上传一个txt一句话木马后出现js弹窗&#xff0c;提示只能上传图片格式文件 说明有前端验证。我的做法是把一句话改为.jpg格式&#xff0c; 然后上传 访问发现虽然上传成功了…

Flutter常用组件实践

Flutter常用组件实践 1、MaterialApp 和 Center(组件居中)2、Scaffold3、Container(容器)4、BoxDecoration(装饰器)5、Column(纵向布局)及Icon(图标)6、Column/Row(横向/横向布局)+CloseButton/BackButton/IconButton(简单按钮)7、Expanded和Flexible8、Stack和Po…

0.机器学习基础

0.人工智能概述&#xff1a; &#xff08;1&#xff09;必备三要素&#xff1a; 数据算法计算力 CPU、GPU、TPUGPU和CPU对比&#xff1a; GPU主要适合计算密集型任务&#xff1b;CPU主要适合I/O密集型任务&#xff1b; 【笔试问题】什么类型程序适合在GPU上运行&#xff1…

系统与网络安全------网络通信原理(4)

资料整理于网络资料、书本资料、AI&#xff0c;仅供个人学习参考。 网络层解析 IP 网络层概述 位于OSI模型第三层作用 定义网络设备的逻辑地址&#xff0c;俗称网络层地址&#xff08;如IP地址&#xff09; 在不同的网段之间选择最佳数据转发路径 协议 IP协议 IP数据包…

Java基础 4.12

1.方法的重载&#xff08;OverLoad&#xff09; 基本介绍 Java中允许同一个类&#xff0c;多个同名方法的存在&#xff0c;但要求形参列表不一致&#xff01; 如 System.out.println(); out是PrintStream类型 重载的好处 减轻了起名的麻烦减轻了记名的麻烦 2.重载的快速入…

XILINX DDR3专题---(1)IP核时钟框架介绍

1.什么是Reference Clock&#xff0c;这个时钟一定是200MHz吗&#xff1f; 2.为什么APP_DATA是128bit&#xff0c;怎么算出来的&#xff1f; 3.APP &#xff1a;MEM的比值一定是1:4吗&#xff1f; 4.NO BUFFER是什么意思&#xff1f; 5.什么情况下Reference Clock的时钟源可…

clickhouse注入手法总结

clickhouse 遇到一题clickhouse注入相关的&#xff0c;没有见过&#xff0c;于是来学习clickhouse的使用&#xff0c;并总结相关注入手法。 环境搭建 直接在docker运行 docker pull clickhouse/clickhouse-server docker run -d --name some-clickhouse-server --ulimit n…

React 组件样式

在这里插入图片描述 分为行内和css文件控制 行内 通过CSS中类名文件控制

利用 pyecharts 实现地图的数据可视化——第七次人口普查数据的2d、3d展示(关键词:2d 、3d 、map、 geo、涟漪点)

参考文档&#xff1a;链接: link_pyecharts 官方文档 1、map() 传入省份全称&#xff0c;date_pair 是列表套列表 [ [ ],[ ] … ] 2、geo() 传入省份简称&#xff0c;date_pair 是列表套元组 [ ( ),( ) … ] 1、准备数据 population_data&#xff1a;简称经纬度 population_da…

解决 Elasticsearch 分页查询性能瓶颈——从10分钟到秒级的优化实践

大家好&#xff0c;我是铭毅天下&#xff0c;一名专注于 Elasticsearch &#xff08;以下简称ES&#xff09;技术栈的技术爱好者。 今天我们来聊聊球友提出的一个实际问题&#xff1a; ES分页查询性能很差&#xff0c;使用from/size方式检索居然需要10分钟&#xff01; 这是一个…

记录IBM服务器检测到备份GPT损坏警告排查解决过程

服务器设备&#xff1a;IBM x3550 M4 Server IMM默认IP地址&#xff1a;192.168.70.125 用户名&#xff1a;USERID 密码&#xff1a;PASSW0RD&#xff08;注意是零0&#xff09; 操作系统&#xff1a;Windows Hyper-V Server 2016 IMM Web System Status Warning&#xff1…

毫米波测试套装速递!高效赋能5G/6G、新材料及智能超表面(RIS)研发

德思特&#xff08;Tesight&#xff09;作为全球领先的测试测量解决方案提供商&#xff0c;始终致力于为前沿技术研发提供高精度、高效率的测试工具。 针对毫米波技术在高频通信、智能超表面&#xff08;RIS&#xff09;、新材料等领域的快速应用需求&#xff0c;我们推出毫米…

Linux中卸载宝塔面板

输入命令 wget http://download.bt.cn/install/bt-uninstall.sh 执行脚本命令 sh bt-uninstall.sh 根据自己的情况选择1还是2 卸载完成校验 bt 这样我们的宝塔面板就卸载完了

无人机的振动与噪声控制技术!

一、振动控制技术要点 1. 振动源分析 气动振动&#xff1a;旋翼桨叶涡脱落&#xff08;如叶尖涡干涉&#xff09;、动态失速&#xff08;Dynamic Stall&#xff09;引发的周期性气动激振力&#xff08;频率与转速相关&#xff09;。 机械振动&#xff1a;电机偏心、传动轴不…

【蓝桥杯】第十六届蓝桥杯 JAVA B组记录

试题 A: 逃离高塔 很简单&#xff0c;签到题&#xff0c;但是需要注意精度&#xff0c;用int会有溢出风险 答案&#xff1a;202 package lanqiao.t1;import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWrit…

OSPF的接口网络类型【复习篇】

OSPF在不同网络环境下默认的不同工作方式 [a3]display ospf interface g 0/0/0 # 查看ospf接口的网络类型网络类型OSPF接口的网络类型&#xff08;工作方式&#xff09;计时器BMA&#xff08;以太网&#xff09;broadcast &#xff0c;需要DR/BDR的选举hello&#xff1a;10s…

python+requests接口自动化测试框架实例教程

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 前段时间由于公司测试方向的转型&#xff0c;由原来的web页面功能测试转变成接口测试&#xff0c;之前大多都是手工进行&#xff0c;利用postman和jmeter进行…

2021第十二届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组

记录刷题的过程、感悟、题解。 希望能帮到&#xff0c;那些与我一同前行的&#xff0c;来自远方的朋友&#x1f609; 大纲&#xff1a; 1、空间-&#xff08;题解&#xff09;-字节单位转换 2、卡片-&#xff08;题解&#xff09;-可以不用当组合来写&#xff0c;思维题 3、直…