Ошибка при освобождении памяти. HEAP[ConsoleApplication1.exe]: Invalid address specified to

Есть код, Который подсчитывает и выводит слова, игнорируя сепараторы. Почему-то при освобождении памяти происходит ошибка.

C:

// Библиотеки
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>

typedef struct Tokens_s {
    int num;
    char **arr;
} Tokens;

// Объявления функций
char* GetString();
bool CheckSeparator(char letter, const char *delims);
void tokensSplit(Tokens *tokens, const char *str, const char *delims);
void PrintMe(Tokens *tokens);
void tokensFree(Tokens *tokens);


// Основной цикл
int main() {

    // Создаем структуру (динамически)
    Tokens* token = (Tokens*)malloc(sizeof(Tokens));

    // Инициализируем структуру
    token->num = 0;
    token->arr = (char**)malloc(sizeof(char*));

    // Инициируем массив разделителей
    const char separators[] = {'.', ',',':',';', 0};

    // Ввод строки
    char* inputStr = GetString();

    // Обработка строки
    tokensSplit(token, inputStr, separators);

    // Освобождение памяти строки
    if (inputStr)
        free(inputStr);

    // Вывод
    PrintMe(token);

    // Освобождение памяти токена
    tokensFree(token);

    return 0;
}

// Получение строки
char* GetString() {

    // Инициализация строки
    int position = 0;
    int buffer = 100;
    const double rate = 1.25;
    char* string = (char*)malloc(buffer*sizeof(char));

    // Считываение строки
    char letter = getchar();
    // && letter != '#' FOR WINDOWS ONLY
    // EOF - -1
    // '\0' - 0
    while (letter != EOF && letter != '#') {
        string[position] = letter;
        buffer--;
        position++;
        if (!buffer) {
            buffer = position * rate;
            // Здесь можно добавить ограничение на 10^6
            string = (char*)realloc(string, (position+buffer)*sizeof (char));
        }
        letter = getchar();
    }

    // Корректируем размер строки и записываем СТОП-символ
    string = (char*)realloc(string, (position+1)*sizeof (char));
    string[position] = 0; // '\0'

    // Возвращаем ответ
    return string;
}

// Проверка на сепаратор
bool CheckSeparator(char letter, const char *delims) {

    if (!delims) {
        printf ("%s", "NullException");
        return false;
    }

    for (int idx = 0; delims[idx] != 0; idx++) {
        if (delims[idx] == letter)
            return true;
    }
    return false;
}

// Обработка строки
void tokensSplit(Tokens *tokens, const char *str, const char *delims) {

 // Проверки на пустоту
 if (!tokens || !str|| !delims) {
     printf ("%s", "NullException");
     return;
 }

 // Пропускаем первые сепараторы
 int idx = 0;
 while (str[idx] != 0 && CheckSeparator(str[idx], delims))
     idx++;

 // Обход строки
 while (str[idx] != 0) {

     // Проверка на сепаратор
     bool IsSeparator = CheckSeparator(str[idx], delims);

     // Если это буква
     if (!IsSeparator) {
         // Вычисляем размер слова
         // Т.к. в текущий символ проверили - пропускаем
         int wordSize = 1;
         while (str[idx+wordSize] != 0 && !CheckSeparator(str[idx+wordSize], delims))
             wordSize++;

         // Выделяем память
         tokens->arr[tokens->num] = (char*)malloc((wordSize+1)*sizeof(char));

         // Копируем слово
         memcpy(tokens->arr[tokens->num], (str+idx), wordSize*sizeof(char));
         tokens->arr[tokens->num][wordSize] = 0;
         tokens->num++;
         
         
         // Перемещаем индекс
         idx += wordSize;
     } else
     // Иначе переходим к следующему символу
     idx++;
 }
}

// Печать такенов
void PrintMe(Tokens *tokens) {

    // Проверки на пустоту
    if (!tokens) {
        printf ("%s", "NullException");
        return;
    }

    // Выводим кол-во слов
    printf("%i\n", tokens->num);

    // Выводим слова
    for (int i = 0; i < tokens->num; i++)
      printf("%s\n", tokens->arr[i]);
}

void tokensFree(Tokens *tokens) {

    // Проверки на пустоту
    if (!tokens)
        return;

    // Удаление строк
    for (int idx = 0; idx < tokens->num; idx++) {
       if (tokens->arr[idx])
           free(tokens->arr[idx]);
    }

    // Удаление массива строк
    if (tokens->arr)
        free(tokens->arr);

    // Удаление токена
    if (tokens)
        free(tokens);
}

Вводим следующие:

..ko,.Privet:kreved,.,:ko:;,,.#

Вылетает ошибка:

HEAP[ConsoleApplication1.exe]: Invalid address specified to RtlValidateHeap( 00EA0000, 00EADDD8 )

Место ошибки четко не выдает, но где-то здесь судя по отладке:

// Удаление строк
for (int idx = 0; idx < tokens->num; idx++) {
   if (tokens->arr[idx])
       free(tokens->arr[idx]);
}

// Удаление массива строк
if (tokens->arr)
    free(tokens->arr);

Ответы (1 шт):

Автор решения: KoVadim

Ваша проблема вот в этой строке

// Выделяем память
tokens->arr[tokens->num] = (char*)malloc((wordSize+1)*sizeof(char));

а чуточку ниже есть

tokens->num++;

то есть, массив arr должен содержать достаточно памяти, что бы вместить массив. Но что там?

token->num = 0;
token->arr = (char**)malloc(sizeof(char*));

а там памяти на один элемент. Что ж. попробуем закостылить

token->num = 0;
token->arr = (char**)malloc(sizeof(char*)*100);

ура, ничего не падает! Ну пока в строке не будет больше 100 "слов". Ок, теперь пофиксим чуточку корректнее.

найдем строку (это где то 130-140 строка кода)

tokens->num++;

и допишем там такое

tokens->arr = realloc(tokens->arr, (tokens->num+1)* sizeof(char*));

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

Для хорошей переделки нужно более корректно выделять память, но это уже отдельная тема.

кстати, писать вот так

arr = realloc(arr, ...)

считается не очень хорошо. если вдруг realloc не сможет выделить память, то будет утечка. Но очень часто либо игнорируют это (в домашних заданиях), либо делают что то такое

tmp = realloc(arr,...);
if (tmp == NULL) {
   // что то плохое случилось. можно выйти, а можно попобовать работать дальше
}
else {
  arr = tmp;
  // все хорошо, работаем
}
→ Ссылка