【问题反馈】JNI 开发:为什么 C++ 在 Debug 正常,Release 却返回 NaN?

【问题反馈】JNI 开发:为什么 C++ 在 Debug 正常,Release 却返回 NaN?
摘要:
在 Android NDK / JNI 开发中,经常会遇到这样一种“诡异”问题:Debug 模式下运行完全正常,而 Release 模式却出现 NaN、Infinity 甚至随机结果。
本文通过一次真实的 JNI 坐标转换案例,深入分析了该问题的根本原因——C++ 返回局部栈内存指针所导致的未定义行为(Undefined Behavior)。

请添加图片描述

【问题反馈】JNI 开发:为什么 C++ 在 Debug 正常,Release 却返回 NaN?

本文为以下问题的解决记录。由于问题较为典型,故梳理备忘。
https://github.com/eqgis/Sceneform-EQR/discussions/16

一、问题现象描述

1. 现象

  • Debug 构建
    • JNI 返回的坐标数值正常
  • Release 构建
    • 返回坐标中出现 NaN / Infinity

且仅在Release出现

2. 出问题的方法

JNIEXPORT void JNICALL Java_com_eqgis_eqr_core_CoordinateUtilsNative_jni_1ToScenePosition( JNIEnv *env, jclass clazz, jdouble ref_x, jdouble ref_y, jdouble target_location_x, jdouble target_location_y, jdouble azimuth_rad, jdoubleArray outJNIArray){double*offset =ComputeTranslation(ref_x, ref_y, target_location_x, target_location_y);double deX =*offset;double deY =*(offset +1);double x = deX *cos(azimuth_rad)- deY *sin(azimuth_rad);double y = deX *sin(azimuth_rad)+ deY *cos(azimuth_rad);double outArray[]={x, y}; env->SetDoubleArrayRegion(outJNIArray,0,2, outArray);}

二、问题根因定位:一个“看起来没问题”的函数

问题最终锁定在这个函数:

double*ComputeTranslation(double x1,double y1,double x2,double y2){double res[2]={0,0};... res[0]= flagX * x; res[1]= flagY * y;return res;}

乍一看逻辑完全正确,但这里隐藏了一个致命错误


三、致命问题:返回了栈内存指针(未定义行为)

1. res 是什么?

double res[2];
  • res函数内部的局部变量
  • 存储在 当前函数的栈帧(stack frame)中

2. 函数返回后发生了什么?

ComputeTranslation 返回时:

  • 函数栈帧被销毁
  • res 对应的内存 立刻失效
  • 返回的指针指向:
    • 已被释放的栈空间
    • 或即将被复用的内存区域

3. C++ 标准如何定义这种行为?

这是典型的 Undefined Behavior(未定义行为)

含义是:

  • 编译器不保证任何结果
  • 程序:
    • 可能“看起来能跑”
    • 也可能随机崩溃
    • 也可能只在 Release 模式出问题

四、为什么 Debug 正常,而 Release 出 NaN?

这是很多开发者最困惑的地方。

1. Debug 模式的特点

  • 编译器优化极少
  • 栈内存分配保守
  • 局部变量:
    • 生命周期“看起来”更长
    • 内存内容不容易被覆盖

返回的指针虽然非法,但数据暂时还在


2. Release 模式的特点

  • 启用 -O2 / -O3 等激进优化
  • 栈空间:
    • 快速复用
    • 指令重排
    • 寄存器替代变量

编译器甚至可能认为:

“你返回这个指针是非法的,那我随便优化”

结果就是:

  • *offset 变成随机值
  • 或直接成为 NaN / Inf

五、为什么 NaN 特别容易出现?

在 Release 下,offset 可能是:

  • 未初始化内存
  • 被 SIMD / 浮点寄存器覆盖
  • 任意 bit pattern

浮点数中

  • 特定 bit pattern ⇒ NaN
  • 一旦参与计算:
    • NaN + x = NaN
    • sin(NaN) = NaN

导致后续都是NaN


六、这不是 JNI 的问题

值得特别强调的是:

  • JNI 只是一个函数调用边界
  • 问题在 C++ 层就已经发生
  • Java 侧只是“忠实地接收了 NaN”

如果这是纯 C++ 工程:

  • 现象 完全一致

七、正确修复方式:返回值语义而不是指针

修复方案:使用结构体返回

structVec2{double x;double y;}; Vec2 ComputeTranslation(double x1,double y1,double x2,double y2){ Vec2 res{0.0,0.0};... res.x = flagX * x; res.y = flagY * y;return res;}

调用:

Vec2 offset =ComputeTranslation(...);double deX = offset.x;double deY = offset.y;

八、为什么“以前这么写也没事”?

原因通常是:

  1. 旧编译器优化弱
  2. Debug 构建长期被使用
  3. 数据规模较小
  4. 没踩到“恰好会覆盖栈”的场景

但:

未定义行为从来不是“偶尔才错”,而是“早晚会炸”。

九、如何系统性避免这类问题?

1. 永远不要返回局部变量地址

return&localVar;return localArray;

2. 优先使用值语义

struct/ std::array / std::pair 

3. Debug ≠ 正确

Debug 只能说明:

“在当前编译条件下恰好没炸”

十、总结

问题结论
Debug 正常不代表代码正确
Release 出 NaN典型 UB 表现
根因返回栈内存指针
JNI 是否有问题没有
正确解法返回结构体 / 值语义

这次问题再次验证了一点:

C++ 中,最危险的 Bug 往往不是“复杂算法”,
而是“看起来理所当然的代码”。

如果在 Debug / Release 行为不一致时遇到诡异问题,
第一时间检查:是否触发了未定义行为。


Read more

【数学建模】用代码搞定无人机烟幕:怎么挡导弹最久?

【数学建模】用代码搞定无人机烟幕:怎么挡导弹最久?

前言:欢迎各位光临本博客,这里小编带你直接手撕**,文章并不复杂,愿诸君耐其心性,忘却杂尘,道有所长!!!! **🔥个人主页:IF’Maxue-ZEEKLOG博客 🎬作者简介:C++研发方向学习者 📖**个人专栏: 《C语言》 《C++深度学习》 《Linux》 《数据结构》 《数学建模》** ⭐️人生格言:生活是默默的坚持,毅力是永久的享受。不破不立,远方请直行! 文章目录 * 一、先搞懂:我们要解决啥问题? * 二、核心计算:代码怎么判断“烟幕有没有用”? * 1. 先算单个烟幕的“有效时间段” * 2. 合并重叠的时间段(避免重复计算) * 3. 只算“导弹到达前”的有效时间 * 三、代码优化:加了2个实用功能,结果直接看 * 1. 跑完直接显示“最优遮蔽时长”

By Ne0inhk
1200PLC与爱普生机器人modbus_TCP通讯

1200PLC与爱普生机器人modbus_TCP通讯

1.前言 首先申明一下我的硬件信息 机器人:C4-A601S 控制器:RC700 PLC:西门子S7-1200(CPU:1217C/DC/DC/DC) 2.控制器IP地址查看及修改 在配置控制器相关信息时需要先用网线连接PC与机器人控制器连接,爱普生机器人出厂设定网址为192.168.0.1(我这里是之前修改过了) 若默认没有显示以太网连接,点击右侧的增加,选择“通过以太网连接到控制器”后点击确定 如果控制器网址被修改过了,不知道是多少,可以用一根PC线,一头接在控制器的“开发用PC连接专用USB端口”另一头接在电脑USB口 这时候再在通讯处选择USB连接就可以通上了 现在就可以在“系统配置”处看到控制器的IP地址以及相关信息了,如果有需要也可以直接在这修改IP地址。 3.机器人控制器配置 网线连接好后开始配置通讯相关信息 1.控制设备 控制设备修改为远程I/O 2.现场总线 现场总线类型修改为“Modbus TCP”

By Ne0inhk

养龙虾-------【多openclaw 对接飞书多应用】---多个大龙虾机器人群聊

🚀 MiniMax Token Plan 惊喜上线!新增语音、音乐、视频和图片生成权益。邀请好友享双重好礼,助力开发体验! 好友立享 9折 专属优惠 + Builder 权益,你赢返利 + 社区特权! 👉 立即参与:https://platform.minimaxi.com/subscribe/token-plan?code=2NMAwoNLlZ&source=link 最近玩了下大龙虾,对接飞书后玩的不亦乐乎,妥妥滴私人助理。但是也萌发一个想法,多个机器人可以自己聊天吗?那会不会把世界给聊翻了。于是我马上搜寻各个配置方式,却是找到了可以配置多个机器人得群聊方式。 1.首先创建多个应用添加机器人,分别和部署得多个openclaw系统对接具体对接参考我写的【 养龙虾-------【openclaw 对接飞书、钉钉、微信 】—移动AI助理】 2.手工拉群并添加机器人: 3.把群id配置进各个龙虾配置文件里面 接下来就可以群聊了

By Ne0inhk
WorkBuddy 使用指南:从零开始配置 QQ 机器人,解锁桌面智能体新玩法

WorkBuddy 使用指南:从零开始配置 QQ 机器人,解锁桌面智能体新玩法

文章目录 * 前言 * 下载 WorkBuddy * 认识 WorkBuddy * 插件类型 * 配置 QQ 机器人 * 登录 QQ 开放平台并注册激活账号 * 配置超级管理员、主体及认证信息 * 创建 QQ 机器人 * 获取 AppID 和 AppSecret * 从 Claw 中获取 Webhook * 在 QQ 开发平台配置回调地址 * 开始使用 WorkBuddy Claw * 总结 前言 在大家还在沉迷于如何搭建 OpenClaw 的时候,腾讯竟然悄悄公测了 WorkBuddy。这是一款面向全角色的桌面智能体,下达指令即可自动生成文档、表格、图表及 PPT 等可视化成果,能够自主规划并交付多模态复杂任务结果,支持多 Agents 并行工作,极致提效,

By Ne0inhk