You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

外部函数完成字符串分词后,字符串数组项出现段错误

命令行工具分词后段错误问题排查

开发运行时命令行工具时遇到一个问题:对用户输入的字符串分词后触发段错误。在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

问题原因与修复方案

核心问题

  1. 指针传递错误:str_utils_split函数中,tokens参数是按值传递的指针,函数内对tokens执行realloc操作时,修改的只是函数内部的局部变量,并不会同步到调用方ui_parse_command里的cmd_tokens指针。调用方的cmd_tokens仍指向最初malloc的内存,而函数内的tokens已经指向新的内存地址,导致调用方持有的指针无法访问正确的token数组,触发段错误。
  2. 数组越界:最后添加终止符时,tokens[*tokens_count + 1] = 0;越界,正确的终止位置应该是tokens[*tokens_count] = NULL;(数组下标从0开始,*tokens_count个元素的最后一个下标是*tokens_count-1,终止符需放在*tokens_count位置)。

修复步骤

  1. 修改str_utils_split的参数类型,改为char ***tokens,让函数能修改调用方的指针:
int str_utils_split(char *str, char *delimiter, char ***tokens,
                    int *tokens_count) {
  1. 函数内部所有操作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;
  1. 修改调用方的调用方式,传入cmd_tokens的地址:
str_utils_split(line, delimiter, &cmd_tokens, &tokens_count);
  1. 可选优化:将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 14:37:35