我正在编写一个解析器(用于 NMEA 句子),它使用 strsep 将字符串拆分为逗号。使用 clang(Apple LLVM 版本 10.0.1)编译时,在拆分具有偶数个标记的字符串时代码会出现段错误。在 Linux 上使用 clang(版本 7.0.1)或 gcc(9.1.1)编译时,代码可以正常工作。
显示该问题的代码的精简版本如下:
#include <stdio.h>
#include <stdint.h>
#include <string.h>
static void gnss_parse_gsa (uint8_t argc, char **argv)
{
}
/**
* Desciptor for a NMEA sentence parser
*/
struct gps_parser_t {
void (*parse)(uint8_t, char**);
const char *type;
};
/**
* List of avaliable NMEA sentence parsers
*/
static const struct gps_parser_t nmea_parsers[] = {
{.parse = gnss_parse_gsa, .type = "GPGSA"}
};
static void gnss_line_callback (char *line)
{
/* Count the number of comma seperated tokens in the line */
uint8_t num_args = 1;
for (uint16_t i = 0; i < strlen(line); i++) {
num_args += (line[i] == ',');
}
/* Tokenize the sentence */
char *args[num_args];
for (uint16_t i = 0; (args[i] = strsep(&line, ",")) != NULL; i++);
/* Run parser for received sentence */
uint8_t num_parsers = sizeof(nmea_parsers)/sizeof(nmea_parsers[0]);
for (int i = 0; i < num_parsers; i++) {
if (!strcasecmp(args[0] + 1, nmea_parsers[i].type)) {
nmea_parsers[i].parse(num_args, args);
break;
}
}
}
int main (int argc, char **argv)
{
char pgsa_str[] = "$GPGSA,A,3,02,12,17,03,19,23,06,,,,,,1.41,1.13,0.85*03";
gnss_line_callback(pgsa_str);
}
段错误发生在 line 上if (!strcasecmp(args[0] + 1, nmea_parsers[i].type)) {
,对 args 的索引操作尝试遵循空指针。
通过手动编辑程序集或添加printf("")
对函数中任何位置的调用来增加堆栈的大小,使其不再出现段错误,就像使args
数组更大(例如,在 中添加一个num_args
)。
总之,以下任何一项都可以防止段错误:
- 使用 clang 10 以外的编译器
- 修改程序集以使动态分配之前的堆栈大小为 80 字节或更多(编译为 64)
- 使用奇数的输入字符串标记
- 分配args
为具有正确数量的标记(或更多)的固定长度数组
- 分配args
为具有至少num_args + 1
元素的可变长度数组
请注意,在 Linux 上使用 clang 7 编译时,动态分配之前的堆栈大小仍然是 64 字节,但代码没有段错误。
我希望有人能够解释为什么会发生这种情况,如果有什么方法可以让这个代码用 clang 10 正确编译。