Сегментационная ошибка на элементах строкового массива после токенизации во внешней функции.

Вопрос или проблема

Я пытаюсь создать утилиту командной строки, и не понимаю, почему у меня возникает ошибка сегментации, когда я пытаюсь токенизировать строку, введенную пользователем.

В файле str_utils.c выражение printf("token: %s\n", tokens[i]); оценивается правильно, и содержимое массива выводится, но в файле user_input.c, если я печатаю указатель с помощью %p, все в порядке, но как только я пытаюсь вывести с помощью %s, возникает ошибка сегментации. Похоже, что я не удаюсь сохранить отдельный токен в массиве 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> ");
  }
}

Я пробовал с локальными копиями и без них, с локальным массивом tmp_tokens в функции разделения и без него, я пытался начать с NULL указателя в user_input.c, я повернул это во всех направлениях, и оно всегда компилируется, но всегда приводит к ошибке сегментации в одном и том же месте.

Правильный вывод был бы (правильный вывод произошел только один раз и снова произошел сбой после того, как я перезапустил программу):

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 ошибка сегментации (сброс ядра)  ./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

Как я мог бы это исправить? Есть идеи? Большое спасибо!

Ответ или решение

Для понимания проблемы с сегментационной ошибкой (segmentation fault), которая возникает в вашем коде после токенизации строк, необходимо рассмотреть несколько ключевых аспектов. Программный код состоит из двух файлов: str_utils.c, где происходит токенизация, и user_input.c, который инициирует процесс токенизации и использует токены после их создания.

Проблема Сегментационной Ошибки

Согласно предоставленному коду, основная проблема возникает из-за неправильного управления памятью, что влечет за собой влияние на указатели на создаваемые токены. В частности, функция str_utils_split пытается передать указатель tokens обратно в вызывающую функцию (ui_parse_command), но модификация указателя внутри функции не отражается на левом указателе (в user_input.c).

Аспекты, способствующие ошибке:

  1. Передача указателя в функцию:
    Когда вы передаете cmd_tokens в str_utils_split, на самом деле вы передаете копию указателя, а не сам указатель. Из-за этого любые изменения, сделанные с tokens внутри str_utils_split, не затрагивают cmd_tokens в ui_parse_command.

  2. Переполнение памяти:
    Вы выделяете память для tokens, но не обеспечиваете правильное возвращение выделенной памяти. Вместо того чтобы изменять сам указатель tokens, необходимо использовать двойной указатель (char ***tokens), чтобы обеспечить правильное изменение.

  3. Ошибочная окончательная инициализация токенов:
    В вашей функции str_utils_split имеется строка, которая выделяет дополнительный байт для установки нулевого окончания, однако вы используете неправильную индексацию при его установке.

Решение Проблемы

Для исправления кода и избежания сегментационных ошибок, потребуется внести изменения в структуру передачи указателей и управления памятью.

Шаги по исправлению

  1. Измените сигнатуру функции str_utils_split:

    int str_utils_split(char *str, char *delimiter, char ***tokens,
                       int *tokens_count) {
  2. Вызовите функцию, передавая адрес указателя cmd_tokens:

    str_utils_split(line, delimiter, &cmd_tokens, &tokens_count);
  3. В теле функции str_utils_split измените выделение памяти и установку токенов:

    *tokens = realloc(*tokens, sizeof(char *) * (*tokens_count));
  4. Убедитесь, что при добавлении нулевого завершения вы используете правильную индексацию:

    (*tokens)[*tokens_count] = NULL; // Чтобы указатель был корректно завершен

Переписанный Код

Вот как может выглядеть исправленный фрагмент вашего кода:

// 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!");
      free(local_str);
      return 1;
    }

    (*tokens)[*tokens_count - 1] = strcpy(calloc(strlen(token) + 1, sizeof(char)), token);
  }

  *tokens = realloc(*tokens, sizeof(char *) * (*tokens_count + 1));
  (*tokens)[*tokens_count] = NULL; // Корректное завершение указателя

  free(local_str);
  return 0;
}
// user_input.c

void ui_parse_command(char *line, ui_command_t *command_obj) {
  char **cmd_tokens = NULL; // Начальное значение NULL
  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: %s\n", cmd_tokens[i]); // Теперь должно работать без ошибки
    free(cmd_tokens[i]); // Не забудьте освободить выделенную память
  }

  free(cmd_tokens); // Освобождаем основной массив после использования
}

Заключение

Теперь ваш код должен работать корректно, без сегментационных ошибок. Важно следить за управлением памятью и передачей многомерных указателей для предотвращения подобных проблем в будущем. Будьте внимательны с индексацией и выделением памяти, чтобы код работал надежно и без ошибок.

Оцените материал
Добавить комментарий

Капча загружается...