外部函数完成字符串分词后,字符串数组项出现段错误
命令行工具分词后段错误问题排查
开发运行时命令行工具时遇到一个问题:对用户输入的字符串分词后触发段错误。在str_utils.c文件中,printf("token: %s\n", tokens[i]);能正常打印数组内容,但在user_input.c文件中,用%p打印指针一切正常,一旦尝试用%s打印就崩溃,看起来没能正确将单个token存储到tokens数组中。
代码示例
str_utils.c
int str_utils_split(char *str, char *delimiter, char **tokens, int *tokens_count) { char *local_str = strdup(str); for (char *token = strtok(local_str, delimiter); token != NULL; token = strtok(NULL, delimiter)) { tokens = realloc(tokens, sizeof(char *) * (++(*tokens_count))); if (tokens == NULL) { fprintf(stderr, "realloc failed! Exiting!"); return 1; } /* 因为看到资料说strtok不分配内存,所以这么写了,是不是这里错了? */ tokens[*tokens_count - 1] = strcpy((char *)calloc(strlen(token) + 1, sizeof(char)), token); } // 还是这里错了? tokens = realloc(tokens, sizeof(char *) * (*tokens_count + 1)); tokens[*tokens_count + 1] = 0; for (int i = 0; i < *tokens_count; i++) { printf("tokenin called fn: %s\n", tokens[i]); } free(local_str); return 0; }
user_input.c
char command_line_buffer[100]; void ui_parse_command(char *line, ui_command_t *command_obj) { char **cmd_tokens = malloc(sizeof(char *)); char *delimiter = " "; int tokens_count = 0; str_utils_split(line, delimiter, cmd_tokens, &tokens_count); for (int i = 0; i < tokens_count; i++) { printf("token in calling fn: %p\n", cmd_tokens[i]); // 正常输出 printf("token in calling fn: %s\n", cmd_tokens[i]); // 触发段错误 } } void ui_start_prompt(void (*callback)(ui_command_t *)) { printf("auralizer> "); ui_command_t command_obj; while (fgets(command_line_buffer, sizeof(command_line_buffer), stdin) != NULL) { ui_parse_command(command_line_buffer, &command_obj); callback(&command_obj); printf("auralizer> "); } }
尝试过的操作
试过使用/不使用本地副本、在split函数中使用/不使用本地tmp_tokens数组、在user_input.c中从NULL指针开始初始化,代码都能编译,但总会在同一位置触发段错误。
不同情况的输出
正确输出(仅出现过一次,重启程序后崩溃)
auralizer> tralala lala token in called fn: tralala token in called fn: lala token in calling fn: tralala token in calling fn: lala
使用printf("%s")时的崩溃输出
auralizer> tralala lala token in called fn: tralala token in called fn: lala [1] 27102 segmentation fault (core dumped) ./bin/auralizer
使用printf("%p")时的指针输出
auralizer> tralala lala token in called fn: tralala token in called fn: lala token in calling fn: 0x557717df4cb0 token in calling fn: 0x557717df4cd0
问题原因与修复方案
核心问题
- 指针传递错误:
str_utils_split函数中,tokens参数是按值传递的指针,函数内对tokens执行realloc操作时,修改的只是函数内部的局部变量,并不会同步到调用方ui_parse_command里的cmd_tokens指针。调用方的cmd_tokens仍指向最初malloc的内存,而函数内的tokens已经指向新的内存地址,导致调用方持有的指针无法访问正确的token数组,触发段错误。 - 数组越界:最后添加终止符时,
tokens[*tokens_count + 1] = 0;越界,正确的终止位置应该是tokens[*tokens_count] = NULL;(数组下标从0开始,*tokens_count个元素的最后一个下标是*tokens_count-1,终止符需放在*tokens_count位置)。
修复步骤
- 修改
str_utils_split的参数类型,改为char ***tokens,让函数能修改调用方的指针:
int str_utils_split(char *str, char *delimiter, char ***tokens, int *tokens_count) {
- 函数内部所有操作
tokens的地方都要解引用:
// 第一次realloc *tokens = realloc(*tokens, sizeof(char *) * (++(*tokens_count))); if (*tokens == NULL) { fprintf(stderr, "realloc failed! Exiting!"); return 1; } // 存储token (*tokens)[*tokens_count - 1] = strcpy((char *)calloc(strlen(token) + 1, sizeof(char)), token); // 添加终止符(修正越界) *tokens = realloc(*tokens, sizeof(char *) * (*tokens_count + 1)); (*tokens)[*tokens_count] = NULL;
- 修改调用方的调用方式,传入
cmd_tokens的地址:
str_utils_split(line, delimiter, &cmd_tokens, &tokens_count);
- 可选优化:将
cmd_tokens初始化为NULL,因为realloc(NULL, size)等价于malloc(size),可省去初始的malloc:
char **cmd_tokens = NULL;
修复后的完整代码
str_utils.c
#include <stdio.h> #include <stdlib.h> #include <string.h> int str_utils_split(char *str, char *delimiter, char ***tokens, int *tokens_count) { char *local_str = strdup(str); if (!local_str) { fprintf(stderr, "strdup failed! Exiting!"); return 1; } *tokens_count = 0; for (char *token = strtok(local_str, delimiter); token != NULL; token = strtok(NULL, delimiter)) { *tokens = realloc(*tokens, sizeof(char *) * (*tokens_count + 1)); if (*tokens == NULL) { fprintf(stderr, "realloc failed! Exiting!"); free(local_str); // 释放已分配的token内存避免泄漏 for (int i = 0; i < *tokens_count; i++) { free((*tokens)[i]); } free(*tokens); return 1; } (*tokens)[*tokens_count] = strcpy((char *)calloc(strlen(token) + 1, sizeof(char)), token); if (!(*tokens)[*tokens_count]) { fprintf(stderr, "calloc failed! Exiting!"); free(local_str); for (int i = 0; i < *tokens_count; i++) { free((*tokens)[i]); } free(*tokens); return 1; } (*tokens_count)++; } // 添加终止NULL *tokens = realloc(*tokens, sizeof(char *) * (*tokens_count + 1)); if (*tokens == NULL) { fprintf(stderr, "realloc failed! Exiting!"); free(local_str); for (int i = 0; i < *tokens_count; i++) { free((*tokens)[i]); } free(*tokens); return 1; } (*tokens)[*tokens_count] = NULL; for (int i = 0; i < *tokens_count; i++) { printf("tokenin called fn: %s\n", (*tokens)[i]); } free(local_str); return 0; }
user_input.c
#include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct { // 根据实际需求定义结构 } ui_command_t; char command_line_buffer[100]; void ui_parse_command(char *line, ui_command_t *command_obj) { char **cmd_tokens = NULL; // 改为NULL初始化 char *delimiter = " "; int tokens_count = 0; if (str_utils_split(line, delimiter, &cmd_tokens, &tokens_count) != 0) { // 处理错误 return; } for (int i = 0; i < tokens_count; i++) { printf("token in calling fn: %p\n", cmd_tokens[i]); printf("token in calling fn: %s\n", cmd_tokens[i]); } // 释放内存避免泄漏 for (int i = 0; i < tokens_count; i++) { free(cmd_tokens[i]); } free(cmd_tokens); } void ui_start_prompt(void (*callback)(ui_command_t *)) { printf("auralizer> "); ui_command_t command_obj; while (fgets(command_line_buffer, sizeof(command_line_buffer), stdin) != NULL) { // 去掉输入末尾的换行符 command_line_buffer[strcspn(command_line_buffer, "\n")] = '\0'; ui_parse_command(command_line_buffer, &command_obj); callback(&command_obj); printf("auralizer> "); } }
内容的提问来源于stack exchange,提问作者dioptrical
相关产品推荐
相关产品推荐

