C语言NMEA-0183协议$GPRMC处理代码段错误排查求助
NMEA-0183 GPS数据处理程序段错误排查
问题描述
我是C语言新手,按NMEA-0183协议编写了GPS数据处理程序,需求如下:
- 从输入的GPS语句中筛选出**校验正确、定位状态字段为'A'**的
$GPRMC语句 - 将UTC时间转换为北京时间后,输出最后一条有效语句的时间
但运行代码时,在if (strcmp(p[0], "$GPRMC") == 0 && *(p[2]+0) == 'A')行出现段错误,需要排查解决。
代码
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <string.h> char a[100]; // 喂入的数据数组 char *time; // 储存时间 int h, m, s, flag = 0; char *check(char *a); int main() { while (1) { memset(a, 0, sizeof(a)); scanf("%s", a); if (a[0] == 'E') break; time = check(a); if (time == NULL) { continue; } else { sscanf(time, "%02d%02d%02d.", &h, &m, &s); h = h + 8; // 换算北京时间 h = h > 24 ? h - 24 : h; flag = 1; } // 检查返回值 } if (flag) printf("%02d:%02d:%02d\n", h, m, s); else printf("No vaild data\n"); return 0; } char *check(char *a) { char *c = a; char *p[20] = {NULL}; char j[100]; // 前检测数组 int answer; // 校验值 int cal = 0; // 计算值 sscanf(c, "$%[^*]*%x", j, &answer); // 拆封 //printf("%s %x",j,answer); for (int s = 0; j[s] != 0; s++) { cal ^= j[s]; } if (cal % 65536 == answer) { for (int i = 0; p[i] != NULL; i++) p[i] = i == 0 ? strtok(c, ",") : strtok(NULL, ","); if (strcmp(p[0], "$GPRMC") == 0 && *(p[2]+0) == 'A') { return p[1]; } } return NULL; }
输入输出示例
- 输入:
$GPRMC,014600.00,A,2237.496474,N,11356.089515,E,0.0,225.5,310518,2.3,W,A*23 $GPRMC,010101.130,A,3606.6834,N,12021.7778,E,0.0,238.3,010807,,,A*6C END
- 期望输出:
09:01:01
段错误原因分析与修复
核心问题:字段拆分循环逻辑错误
check函数中拆分NMEA字段的循环用了for (int i = 0; p[i] != NULL; i++),但p数组初始全为NULL,循环直接终止,导致p[0]、p[2]都是空指针。后续调用strcmp、访问p[2][0]时,会触发空指针访问的段错误。
修复步骤
- 修正字段拆分逻辑:改为循环调用
strtok直到返回NULL,确保p数组被正确填充。 - 添加空指针检查:使用
p数组元素前,先判断是否为NULL,避免非法访问。 - 优化校验逻辑:NMEA校验和是单字节值,直接比较
cal和answer即可,无需取模65536。
修复后的代码
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <string.h> char a[100]; // 喂入的数据数组 char *time; // 储存时间 int h, m, s, flag = 0; char *check(char *a); int main() { while (1) { memset(a, 0, sizeof(a)); scanf("%s", a); if (strncmp(a, "END", 3) == 0) // 更准确的终止判断,避免误判以E开头的合法语句 break; time = check(a); if (time == NULL) { continue; } else { sscanf(time, "%02d%02d%02d.", &h, &m, &s); h += 8; // 换算北京时间 if (h >= 24) // 修正逻辑:UTC+8后等于24时需转为0点 h -= 24; flag = 1; } } if (flag) printf("%02d:%02d:%02d\n", h, m, s); else printf("No valid data\n"); // 修正拼写错误vaild→valid return 0; } char *check(char *a) { char *c = a; char *p[20] = {NULL}; char j[100]; int answer; int cal = 0; // 校验格式是否正确,避免非法输入 if (sscanf(c, "$%[^*]*%x", j, &answer) != 2) return NULL; // 计算校验和 for (int s = 0; j[s] != '\0'; s++) { cal ^= j[s]; } // 校验和不匹配直接返回 if (cal != answer) return NULL; // 拆分NMEA字段,避免数组越界 int i = 0; p[i] = strtok(c, ","); while (p[i] != NULL && i < 19) { i++; p[i] = strtok(NULL, ","); } // 检查必要字段是否存在,以及语句类型和定位状态 if (p[0] != NULL && strcmp(p[0], "$GPRMC") == 0 && p[2] != NULL && p[2][0] == 'A') { return p[1]; } return NULL; }
额外优化点
- 终止判断改为
strncmp(a, "END", 3) == 0,避免误判以E开头的合法GPS语句。 - 修正北京时间转换逻辑:当h等于24时转为0点(如UTC16:00转北京时间24:00,应显示00:00)。
- 修复拼写错误
vaild→valid。 - 添加
sscanf返回值检查,处理格式错误的输入。 - 字段拆分时限制循环次数,避免数组越界。
内容的提问来源于stack exchange,提问作者spric
相关产品推荐
相关产品推荐

