如何安全检查 `struct gps_data_t*` 是否为 NULL?
结论
判断 gps_data_t* 是否为空,直接写 response == nullptr 或 !response 即可。这两种写法只比较指针值,不会解引用指针,也不会调用 strcmp():
gps_data_t* response =
instance->gps_handler.stream(WATCH_ENABLE | WATCH_JSON);
if (response == nullptr) {
std::cerr << "Unable to start GPSD stream.\n";
return nullptr;
}
当前的段错误发生在 gpsmm::stream() 返回之前。函数调用还没有完成,程序也就没有机会检查返回值,因此问题不在 == nullptr。
为什么回溯中会出现 strcmp()
下面的表达式比较的是指针值:
response == nullptr
编译器不会把它转换为字符串比较。只有比较 C 字符串内容时,才会使用 strcmp()。
以下两种写法的执行顺序相同:
if (!(response = instance->gps_handler.stream(...)))
if ((response = instance->gps_handler.stream(...)) == nullptr)
程序都会先调用 stream(),然后在函数返回后判断结果。
给出的回溯是:
strcmp()
gps_stream()
gpsmm::stream(int)
tmc::Initialize()
从调用栈看,strcmp() 是在 gps_stream() 内部调用的。崩溃时 gpsmm::stream() 尚未返回,所以更改调用方的空指针判断方式解决不了这个问题。
建议写法
把函数调用和结果判断分开,代码更容易阅读,也方便在调试器中逐步检查:
tmc* tmc::Initialize()
{
tmc* instance = new tmc();
gps_data_t* response =
instance->gps_handler.stream(WATCH_ENABLE | WATCH_JSON);
if (response == nullptr) {
std::cerr << "Unable to start GPSD stream.\n";
delete instance;
return nullptr;
}
// 使用 response,但不要在检查前解引用它。
return instance;
}
也可以写成:
if (!response) {
// response is null
}
在 C++11 及更高版本中,通常使用 nullptr,不要用整数 0 表示空指针。
原示例中 Initialize() 的返回类型是 tmc*,因此每条执行路径都必须返回一个值。成功路径缺少 return instance; 会导致未定义行为,但这与当前回溯中的 strcmp() 崩溃没有直接关系。
需要排查的实际问题
崩溃发生在库内部,接下来应检查传给 gpsmm/libgps 的状态和参数。
1. 检查构造参数
确认主机名和端口最终都是有效且以 '\0' 结尾的字符串,并保证它们在整个使用期间有效:
tmc::tmc()
: gps_handler("localhost", DEFAULT_GPSD_PORT)
{
}
如果 DEFAULT_GPSD_PORT 来自当前安装的 gpsd 头文件,通常应保留它原本的类型,不要进行类型不匹配的强制转换。如果实际代码传入动态字符缓冲区,还要确认缓冲区没有提前销毁。
2. 检查头文件与运行库是否匹配
确认编译时使用的 gpsd/libgps 头文件,与运行时加载的 libgps.so.30 及 C++ 包装库来自兼容的安装。升级或更换库后,需要清理旧目标文件并完整重新编译程序。
可以先查看进程实际加载了哪个库:
ldd ./your_program | grep -E 'gps|gpsmm'
如果系统中既有发行版安装的 gpsd,也有手工安装的版本,例如分别位于 /usr/lib 和 /usr/local/lib,需要重点检查头文件与共享库是否被混用。
3. 在 GDB 中检查库调用参数
继续检查 gps_stream() 对应的栈帧:
bt full
frame 1
info args
info locals
frame 2
info args
如果系统安装了对应版本的调试符号,还可以追踪 strcmp() 的两个实参来自哪里。现有回溯只能确定崩溃发生在库调用链中,无法判断具体哪个字符串参数为 NULL。
4. 排除调用前的内存破坏
无效的 gpsmm 对象、越界写入、对象生命周期错误或 ABI 不匹配,都可能导致库内部收到损坏的数据。条件允许时,可以用 AddressSanitizer 重新编译应用程序:
-g -O1 -fsanitize=address,undefined -fno-omit-frame-pointer
如果应用程序此前发生过越界写入,Sanitizer 通常能在程序最终崩溃于 strcmp() 之前报告错误。
stream() 成功不等于已经获得定位
stream() 用于启用 gpsd 数据流。它返回非空指针,只能说明这一步没有返回空值,不能证明设备已经完成二维或三维定位。即使 gpsd 正常运行,在卫星数量不足时,连接和数据流仍可能正常,但 fix 状态尚不可用。
代码需要分别处理数据流状态和定位状态:
gps_data_t* response =
instance->gps_handler.stream(WATCH_ENABLE | WATCH_JSON);
if (response == nullptr) {
// 数据流启动失败、连接异常或库报告错误。
return nullptr;
}
// 后续等待并读取数据,再依据当前 libgps 版本提供的
// set 标志及 response->fix.mode 判断定位数据是否有效。
应检查哪些 set 位和 fix 字段,需要以编译时使用的 gpsd 3.25 头文件/API 定义为准。在确认相应的有效性标志之前,不能仅凭指针非空就读取经纬度。
response == nullptr 本身是安全的。现有回溯把问题定位在 gps_stream() 内部,应继续检查构造参数、库版本是否匹配、对象生命周期以及是否存在内存破坏。
备注:内容仅供参考。
