Разделение строки на токены и помещение токенов в массив - strtok - PullRequest
0 голосов
/ 11 ноября 2018
char** token_arr(char* str, int n_tokens)
{
   char**arr = malloc((n_tokens+1)*sizeof(char*));
   char str2[n_tokens + 1];
   strcpy(str2,str);
   int i = 0;
   char *p = strtok (str2, " ");

   while (p != NULL)
   {
       arr[i] = p;
       //printf("%s\n", arr[i]);
       p = strtok (NULL, " ");
       i++;
   }
 return arr;
}

Цель token_arr - получить строку и количество токенов, а затем поместить токены в массив.Возвращается массив токенов.

int main(void) {
  char*str1 = "( 8 + ( 41 - 12 ) )";
  char**expression = token_arr(str1, 9);
  for(int i = 0; i < 9; i++)
    printf("expression[%d] = %c\n", i, *expression2[i]);
 return 0;
}

Вывод:

expression2[0] = (
expression2[1] = 
expression2[2] = 
expression2[3] = 
expression2[4] = 
expression2[5] = 
expression2[6] = 
expression2[7] = 
expression2[8] =

Почему печатается только первое значение?Что не так с моим кодом?

1 Ответ

0 голосов
/ 11 ноября 2018

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

Как вы узнали, когда вы объявляете str2 локально для token_arr, оно имеет длительность автоматического хранения и действует только в той области, в которой оно объявлено. Когда возвращается token_arr, память, содержащая str2, освобождается для повторного использования, и любая попытка сослаться на эту память обратно в main() вызывает Неопределенное поведение .

Какие у вас варианты? (1) используйте strdup для динамического выделения памяти для каждого токена, скопируйте токен в новую выделенную память и затем назначьте начальный адрес для нового блока памяти, содержащего токен, на arr[i], например

        arr[i] = strdup (p);

или (2) сделать то же самое вручную, используя strlen, malloc & memcpy, например,

        size_t len = strlen(p);
        arr[i] = malloc (len + 1);
        /* validate - here */
        memcpy (arr[i], p, len + 1);

Теперь каждый arr[i] указывает на блок памяти с выделенной продолжительностью хранения , который остается действительным до тех пор, пока free не будет вызвано для этого блока - или программа не завершится.

Что если будет найдено меньше n_tokens?

Если в token_arr обнаружено меньше n_tokens и вы пытаетесь использовать n_tokens - expressions обратно в main(), вы, скорее всего, снова вызовете Неопределенное поведение . Чтобы убедиться, что вы используете только токены, найденные в token_arr и доступные в main() с помощью присваивания expression - Передайте указатель на n_tokens в качестве второго параметра и обновите его, значение i перед вами return arr;, например

char **token_arr (const char *str, int *n_tokens)
{
    char **arr = malloc(*n_tokens * sizeof *arr);
    ...
        i++;
    }
    *n_tokens = i;  /* assign i to make tokes assigned available */

    return arr;
}

Теперь n_tokens в main() содержит только количество токенов, фактически найденных и выделенных для arr[i] в token_arr.

.

Проверка каждого распределения

Очень важно, чтобы вы проверяли каждый вызов malloc, calloc, realloc, strdup или любой другой функции, которая выделяет вам память. Распределение может потерпеть неудачу. Когда это произойдет, он даст вам знать, возвращая NULL вместо указателя, содержащего начальный адрес для нового блока памяти. Проверьте каждое распределение.

В целом, вы можете сделать что-то вроде:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char **token_arr (const char *str, int *n_tokens)
{
    char **arr = malloc(*n_tokens * sizeof *arr);
    char str2 [strlen(str) + 1];
    int i = 0;

    if (!arr) { /* validate every allocation */
        perror ("malloc-n_tokens");
        return NULL;
    }

    strcpy (str2, str);

    char *p = strtok (str2, " ");

    while (i < *n_tokens && p != NULL) {    /* check used pointers */
        arr[i] = strdup (p);
        if (!arr[i]) {  /* strdup allocates -> you must validate */
            perror ("strdup-arr[i]");
            if (i)          /* if tokens stored, break an return */
                break;
            else {          /* if no tokes stored, free pointers */
                free (arr);
                return NULL;
            }
        }
        p = strtok (NULL, " ");
        i++;
    }
    *n_tokens = i;  /* assign i to make tokes assigned available */

    return arr;
}

int main (void) {

    char *str1 = "( 8 + ( 41 - 12 ) )";
    int n_tokens = 9;
    char **expression = token_arr (str1, &n_tokens);

    if (expression) {       /* validate token_arr succeeded */
        for (int i = 0; i < n_tokens; i++) { /* n_tokens times */
            printf ("expression[%d] = %s\n", i, expression[i]);
            free (expression[i]);   /* free mem allocated by strdup */
        }
        free (expression);
    }

    return 0;
}

( примечание: аналогично проверьте возврат token_arr перед использованием возврата)

Пример использования / Вывод

$ ./bin/token_arr
expression[0] = (
expression[1] = 8
expression[2] = +
expression[3] = (
expression[4] = 41
expression[5] = -
expression[6] = 12
expression[7] = )
expression[8] = )

Использование памяти / проверка ошибок

В любом написанном вами коде, который динамически распределяет память, у вас есть 2 обязанностей относительно любого выделенного блока памяти: (1) всегда сохраняйте указатель на начальный адрес для блока памяти, так что, (2) он может быть освобожден , когда он больше не нужен.

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

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

$ valgrind ./bin/token_arr
==8420== Memcheck, a memory error detector
==8420== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==8420== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==8420== Command: ./bin/token_arr
==8420==
expression[0] = (
expression[1] = 8
expression[2] = +
expression[3] = (
expression[4] = 41
expression[5] = -
expression[6] = 12
expression[7] = )
expression[8] = )
==8420==
==8420== HEAP SUMMARY:
==8420==     in use at exit: 0 bytes in 0 blocks
==8420==   total heap usage: 10 allocs, 10 frees, 92 bytes allocated
==8420==
==8420== All heap blocks were freed -- no leaks are possible
==8420==
==8420== For counts of detected and suppressed errors, rerun with: -v
==8420== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

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

Посмотрите вещи и дайте мне знать, если у вас есть дополнительные вопросы.

...