问题描述
把 Unity 工程编译到 Android 上运行后,出现了一个必现的界面卡死bug。
表现为:每次轮到自己出大招时,必现界面卡死,但程序不会 crash。在 Unity 编辑器和 iOS 下都无法重现。
分析日志发现,卡死前最后调用了算法库中的一个函数。
算法库是 c++ 写的,用 cmake 组织,编译成 so 给 C# 层调用。
定位
怀疑是哪里死循环了。
想试下能否通过工具来定位,使用 Android Studio 的 Thread-Dump 、Unity Attach(死活不成功)发现帮助都不大。
最后还是在算法库里添加了大量日志后,发现在形如下面的代码中死循环!
for(char i = 0;i >=0;–i)
{
i = -1;
printf(“i0 = %d\n”,i);
i += 1;
printf(“i1 = %d\n”,i);
}
上面代码合理应该输出 -1,0 ,结束循环。
然而,在 Android 下却死循环了!结果是 255,0,255,0,255,…,感觉像是溢出后回环了
分析
google 一下发现:
在 x86 下,char 默认是 有符号。
在 ARM 架构下,char 默认是 无符号,即 Android 这种情况。i = 0,–i 变成 -1,无符号下就是 1111 1111 = 255,再+1就溢出了,变成0,无限循环。
而 iOS 虽然也是 ARM 架构,但它的 char 默认却是有符号的,所以也没出问题。
死循环代码快速定位方法
定位死循环代码可以是一个挑战,尤其是在大型项目中。以下是一些快速定位死循环的有效方法和工具,帮助你更高效地找到问题所在:
1. 使用日志记录
-
添加日志:在循环的关键位置添加日志输出,记录循环的迭代次数和变量的状态。这可以帮助你确定循环是否在按预期运行。
for (int i = 0; i < MAX_ITERATIONS; ++i) { printf("Iteration: %d\n", i); // 其他逻辑 }
-
条件日志:在循环中添加条件日志,记录特定条件下的变量值,以便更好地理解循环的行为。
2. 使用调试器
-
设置断点:在循环的开始和结束处设置断点,观察变量的变化。如果循环没有正常结束,可以逐步调试,查看变量的状态。
-
观察变量:在调试器中观察循环变量的值,查看它是否在预期范围内变化。
3. 使用线程转储(Thread Dump)
-
生成线程转储:在 Android Studio 或其他 IDE 中生成线程转储,查看当前线程的状态。死循环的线程通常会在某个位置反复执行相同的代码。
-
分析堆栈:分析线程堆栈,查找循环的调用栈,确定死循环的具体位置。
4. 使用静态分析工具
- 静态分析:使用静态分析工具(如 Clang-Tidy、Cppcheck)来检查代码中的潜在问题。这些工具可以帮助识别可能导致死循环的代码模式。
5. 代码审查
- 同行审查:让其他开发者审查你的代码,可能会发现你自己未注意到的问题。不同的视角可能会帮助识别潜在的逻辑错误。
6. 使用时间限制
-
超时机制:在循环中添加超时机制,如果循环运行超过预定时间,则输出警告并退出。这可以帮助你在开发过程中快速发现死循环。
auto start = std::chrono::high_resolution_clock::now(); while (condition) { // 循环逻辑 auto now = std::chrono::high_resolution_clock::now(); if (std::chrono::duration_cast<std::chrono::seconds>(now - start).count() > TIME_LIMIT) { printf("Loop exceeded time limit!\n"); break; } }
通过结合这些方法,你可以更快速地定位和解决死循环问题。希望这些建议对你有所帮助!如果你有其他问题或需要进一步讨论的内容,欢迎继续交流!
问题背景
在 Android NDK 开发中,char
类型的符号性可能会因平台而异。在 x86 架构上,char
默认是有符号的,而在 ARM 架构上,char
默认是无符号的。这种差异可能导致在某些情况下出现意想不到的行为,例如所遇到的死循环问题。
解决方案
-
使用 CMake 显式指定
char
符号性:- 在 CMakeLists.txt 文件中,可以通过添加编译选项来确保
char
被视为有符号类型。可以在 CMake 文件中添加以下行:
add_compile_options(-fsigned-char)
或者,如果你使用的是
target_compile_options
,可以这样写:target_compile_options(your_target_name PRIVATE -fsigned-char)
- 在 CMakeLists.txt 文件中,可以通过添加编译选项来确保
-
使用 CMake 的
CMAKE_C_FLAGS
和CMAKE_CXX_FLAGS
:- 你也可以直接在 CMakeLists.txt 中设置全局的 C 和 C++ 编译标志:
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsigned-char") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsigned-char")
其他建议
-
使用标准类型:
- 为了避免与平台相关的类型问题,建议使用 C++ 标准库中的固定宽度整数类型,如
std::int8_t
和std::uint8_t
。这些类型在所有平台上都是一致的,能够避免因符号性引起的问题。
- 为了避免与平台相关的类型问题,建议使用 C++ 标准库中的固定宽度整数类型,如
-
编写文档:
- 在项目文档中记录下关于
char
符号性的注意事项,特别是在跨平台开发时,帮助团队成员理解潜在的陷阱。
- 在项目文档中记录下关于
-
测试和验证:
- 在不同平台上进行充分的测试,确保代码在所有目标平台上都能正常工作。可以使用 CI/CD 工具来自动化这一过程。
-
代码审查:
- 在代码审查过程中,特别注意数据类型的使用,确保在跨平台代码中使用了合适的类型。
通过这些方法,可以有效减少因数据类型符号性引起的问题,提高代码的可移植性和稳定性。如果你有其他问题或需要进一步讨论的内容,欢迎继续交流!