Недопустимая неинициализированная ошибка перехода или перемещения памяти при попытке вручную разделить строку char32_t на токены

Я пытаюсь разделитьchar32_tстроку на токены, разделенные разделителем. Я не использую какую-либо функцию strtok или другую библиотеку std, потому что предполагается, что входная строка и разделитель будут многобайтовой строкой Юникода.

Вот функция, которую я написал:

      #include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <uchar.h>
#include <wchar.h>

char32_t **sp(char32_t *str, char32_t delim, int *len) {
  *len = 1;
  char32_t *s = str;
  while (*s != U'\0') {
    if (*s == delim) {
      (*len)++;
    }
    s++;
  }
  char32_t **tokens = (char32_t **)malloc((*len) * sizeof(char32_t *));
  if (tokens == NULL) {
    exit(111);
  }

  char32_t * p = str;
  int i = 0;
  while (*p != U'\0') {
    int tok_len = 0;
    while (p[tok_len] != U'\0' && p[tok_len] != delim) {
      tok_len++;
    }
    tokens[i] = (char32_t *)malloc(sizeof(char32_t) * (tok_len + 1));
    if (tokens[i] == NULL) {
      exit(112);
    }
    memcpy(tokens[i], p, tok_len * sizeof(char32_t));
    tokens[i][tok_len] = U'\0';
    p += tok_len + 1;
    i++;
  }
  return tokens;
}

А вот код драйвера

      int main() {
  char32_t *str = U"Hello,World,mango,hey,";
  char32_t delim = U',';
  int len = 0;
  char32_t ** tokens = sp(str, delim, &len);
  wprintf(L"len -> %d\n", len);
  for (int i = 0; i < len; i++) {
    if (tokens[i]) {
    
    wprintf(L"[%d] %ls\n" , i , tokens[i]);  
    }
    free(tokens[i]);
  }  
  free(tokens);

}

Вот результат:

      len -> 5
[0] Hello
[1] World
[2] mango
[3] hey
[4] (null)

Но когда я проверяю программу с помощью valgrind, она показывает несколько ошибок памяти.

      valgrind -s --leak-check=full --track-origins=yes ./x3
==7703== Memcheck, a memory error detector
==7703== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==7703== Using Valgrind-3.20.0 and LibVEX; rerun with -h for copyright info
==7703== Command: ./x3
==7703== 
tok -> 5
tok -> 5
tok -> 5
tok -> 3
len -> 5
[0] Hello
[1] World
[2] mango
[3] hey
==7703== Conditional jump or move depends on uninitialised value(s)
==7703==    at 0x48FDAF8: __wprintf_buffer (vfprintf-process-arg.c:396)
==7703==    by 0x48FF421: __vfwprintf_internal (vfprintf-internal.c:1459)
==7703==    by 0x490CFAE: wprintf (wprintf.c:32)
==7703==    by 0x1093C9: main (main.c:51)
==7703==  Uninitialised value was created by a heap allocation
==7703==    at 0x4841888: malloc (vg_replace_malloc.c:393)
==7703==    by 0x1091FC: sp (main.c:17)
==7703==    by 0x109384: main (main.c:47)
==7703== 
[4] (null)
==7703== Conditional jump or move depends on uninitialised value(s)
==7703==    at 0x4844225: free (vg_replace_malloc.c:884)
==7703==    by 0x1093DA: main (main.c:52)
==7703==  Uninitialised value was created by a heap allocation
==7703==    at 0x4841888: malloc (vg_replace_malloc.c:393)
==7703==    by 0x1091FC: sp (main.c:17)
==7703==    by 0x109384: main (main.c:47)
==7703== 
==7703== 
==7703== HEAP SUMMARY:
==7703==     in use at exit: 0 bytes in 0 blocks
==7703==   total heap usage: 7 allocs, 7 frees, 5,248 bytes allocated
==7703== 
==7703== All heap blocks were freed -- no leaks are possible
==7703== 
==7703== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
==7703== 
==7703== 1 errors in context 1 of 2:
==7703== Conditional jump or move depends on uninitialised value(s)
==7703==    at 0x4844225: free (vg_replace_malloc.c:884)
==7703==    by 0x1093DA: main (main.c:52)
==7703==  Uninitialised value was created by a heap allocation
==7703==    at 0x4841888: malloc (vg_replace_malloc.c:393)
==7703==    by 0x1091FC: sp (main.c:17)
==7703==    by 0x109384: main (main.c:47)
==7703== 
==7703== 
==7703== 1 errors in context 2 of 2:
==7703== Conditional jump or move depends on uninitialised value(s)
==7703==    at 0x48FDAF8: __wprintf_buffer (vfprintf-process-arg.c:396)
==7703==    by 0x48FF421: __vfwprintf_internal (vfprintf-internal.c:1459)
==7703==    by 0x490CFAE: wprintf (wprintf.c:32)
==7703==    by 0x1093C9: main (main.c:51)
==7703==  Uninitialised value was created by a heap allocation
==7703==    at 0x4841888: malloc (vg_replace_malloc.c:393)
==7703==    by 0x1091FC: sp (main.c:17)
==7703==    by 0x109384: main (main.c:47)
==7703== 
==7703== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

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

Я также пробовал использовать строки Юникода, такая же ошибка возникает.

1 ответ

выдает эти ошибки, потому что ваша программа обращается к неинициализированной памяти в последней итерации этогоforциклическая функция (т.е. при доступе к tokens[4], когда lenзначение 5) :

        for (int i = 0; i < len; i++) {
     if (tokens[i]) {
        wprintf(L"[%d] %ls\n" , i , tokens[i]);  
     }
     free(tokens[i]);
  }  

mallocфункция выделяет память и оставляет ее неинициализированной. Здесь, в функции, когда ваша программа выделяет память, она не инициализируется:

        char32_t **tokens = (char32_t **)malloc((*len) * sizeof(char32_t *));

Цикл функции выделяет и копирует некоторое значение в выделенную память для всех элементов массива, кроме последнего, и оставляет его неинициализированным. вmain(), ваша программа обращается к этому неинициализированному элементу и, следовательно, сообщает об ошибке.

Чтобы устранить проблему, в функции после выделения памяти —
Либо создайте последний указательный элемент массива токенов:

      // this is the bare minimum change required to fix the problem
tokens [*len - 1] = NULL;

Или сделайте все указатели

      for (int i = 0; i < *len; ++i) {
   tokens[i] = NULL;
}

Или используйтеcallocдля выделения памяти, что обеспечит инициализацию всех выделенных указателей:

      char32_t **tokens = calloc((*len), sizeof(char32_t *));

С любым из вышеупомянутых решений,valgrindвыход:

      # valgrind -s --leak-check=full --track-origins=yes ./a.out 
==9761== Memcheck, a memory error detector
==9761== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==9761== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info
==9761== Command: ./a.out
==9761== 
len -> 5
[0] Hello
[1] World
[2] mango
[3] hey
==9761== 
==9761== HEAP SUMMARY:
==9761==     in use at exit: 0 bytes in 0 blocks
==9761==   total heap usage: 7 allocs, 7 frees, 5,248 bytes allocated
==9761== 
==9761== All heap blocks were freed -- no leaks are possible
==9761== 
==9761== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Обнаружил еще одну проблему в вашем коде: когда входная строка не имеет символа-разделителя в качестве последнего символа, ваша программа в конечном итоге получает доступ к входной строке за пределами ее длины, что приводит к неопределенному поведению . Посмотрите на этот оператор цикла функции:

      p += tok_len + 1;

Предположим, что входная строка -U"Hello,World,mango,hey" [обратите внимание, что последний символ строки не является разделителем ,] . Вложенное условие цикла while приведет кfalseкогдаp[tok_len]равноU'\0'при повторении входной строки и приведенного ниже оператораp += tok_len + 1;сделает указатель указывающим на память сразу за входной строкой. Условие внешнего цикла пытается разыменовать, и это приведет к неопределенному поведению.

Замените этот оператор циклаsp()функция:

          p += tok_len + 1;

с этим

          p += tok_len;
    p += (*p != '\0') ? 1 : 0;

Сначала указатель будет указывать на один символ после конца текущих токенов во входной строке, и если этот символ не является нулевым завершающим символом, то только1будет добавлено к указателюp, иначе нет.

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


РЕДАКТИРОВАТЬ:

Это ваше требование: если последний символ входной строки является разделителем, то последний членtokensмассив должен указывать на пустую строку, а не наNULL.
Вам не нужно обрабатывать это как особый сценарий после цикла, как вы показали в комментарии. Вы можете обработать это в теле цикла, который обрабатывает входную строку и извлекает из нее токены, например:

      char32_t **sp(const char32_t *str, const char32_t delim, int *len) {
    *len = 1;
    for (int i = 0; str[i] != U'\0'; ++i) {
        if (str[i] == delim) (*len)++;
    }

    char32_t **tokens = malloc ((*len) * sizeof (char32_t *));
    if (tokens == NULL) {
        exit(111);
    }

    int start = 0, end = 0, i = 0;
    do {
        if ((str[end] == delim) || (str[end] == U'\0')) {
            tokens[i] = malloc (sizeof (char32_t) * (end - start + 1));
            if (tokens[i] == NULL) {
                exit(112);
            }
            memcpy (tokens[i], &str[start], sizeof (char32_t) * (end - start));
            tokens[i][end - start] = U'\0';
            start = end + 1; i++;
        }
    } while (str[end++] != U'\0');

    return tokens;
}

Несколько тестовых случаев:

Строка ввода:

      char32_t *str = U"Hello,World,mango,hey,";

Выход:

      # ./a.out 
len -> 5
[0] Hello
[1] World
[2] mango
[3] hey
[4] 

Строка ввода:

      char32_t *str = U"Hello,World,mango,hey";

Выход:

      # ./a.out 
len -> 4
[0] Hello
[1] World
[2] mango
[3] hey

Строка ввода:

      char32_t *str = U",,, , u";

Выход:

      # ./a.out 
len -> 5
[0] 
[1] 
[2] 
[3]  
[4]  u

Строка ввода:

      char32_t *str = U" ";

Выход:

      # ./a.out 
len -> 1
[0]  
Другие вопросы по тегам