JNI 开发:C++ Debug 正常 Release 返回 NaN 的根因分析
JNI 开发中 C++ 代码在 Debug 模式运行正常,但 Release 模式返回 NaN。根本原因是函数返回了局部栈内存指针,导致未定义行为。Debug 模式下栈内存未被覆盖所以看似正常,Release 优化后内存被复用或随机化导致数据错误。修复方案是避免返回局部变量地址,改用结构体值语义返回。

JNI 开发中 C++ 代码在 Debug 模式运行正常,但 Release 模式返回 NaN。根本原因是函数返回了局部栈内存指针,导致未定义行为。Debug 模式下栈内存未被覆盖所以看似正常,Release 优化后内存被复用或随机化导致数据错误。修复方案是避免返回局部变量地址,改用结构体值语义返回。

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


NaN / Infinity
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;
}
乍一看逻辑完全正确,但这里隐藏了一个致命错误。
res 是什么?double res[2];
res 是 函数内部的局部变量当 ComputeTranslation 返回时:
res 对应的内存 立刻失效这是典型的 Undefined Behavior(未定义行为)
含义是:
这是很多开发者最困惑的地方。
返回的指针虽然非法,但数据暂时还在
-O2 / -O3 等激进优化编译器甚至可能认为:
'你返回这个指针是非法的,那我随便优化'
结果就是:
*offset 变成随机值NaN / Inf在 Release 下,offset 可能是:
而 浮点数中:
NaNNaN + x = NaNsin(NaN) = NaN导致后续都是 NaN
值得特别强调的是:
如果这是纯 C++ 工程:
struct Vec2 {
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;
原因通常是:
但:
未定义行为从来不是'偶尔才错',而是'早晚会炸'。
return &localVar;
return localArray;
struct / std::array / std::pair
Debug 只能说明:
'在当前编译条件下恰好没炸'
| 问题 | 结论 |
|---|---|
| Debug 正常 | 不代表代码正确 |
| Release 出 NaN | 典型 UB 表现 |
| 根因 | 返回栈内存指针 |
| JNI 是否有问题 | 没有 |
| 正确解法 | 返回结构体 / 值语义 |
这次问题再次验证了一点:
C++ 中,最危险的 Bug 往往不是'复杂算法', 而是'看起来理所当然的代码'。
如果在 Debug / Release 行为不一致时遇到诡异问题, 第一时间检查:是否触发了未定义行为。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online