c - 将字符串拆分为标记并将标记放入数组 - strtok

标签 c arrays output strtok

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的作用是得到一个字符串和一些token,然后把token放到一个数组中。返回标记数组。

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] =

为什么只打印第一个值?我的代码有什么问题?

最佳答案

虽然我认为您可能已经根据评论对大部分问题进行了排序,但让我们看一下如何解决 expressions 的验证/返回以及返回 token 数量的方法以防止标记化错误导致少于 n_tokens 被找到。

正如你所了解的,当你声明 token_arr 本地的 str2 时,它具有自动存储持续时间并且仅在所在的范围内有效它被宣布。当 token_arr 返回时,保存 str2 的内存被释放以供重新使用,任何在 main() 中引用该内存的尝试都会调用 未定义的行为

您有哪些选择? (1)使用strdup为每个token动态分配存储,将token复制到新分配的内存中,然后将包含token的新内存块的起始地址赋值给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] 都指向一个具有分配的存储持续时间 的内存块,该内存块一直有效,直到对该 block 调用 free -- 或者程序结束。

如果找到少于 n_tokens 怎么办?

如果在 token_arr 中发现少于 n_tokens 并且您尝试通过 expressions 中使用 n_tokens code>main() 您可能会再次调用未定义的行为。为确保您仅使用在 token_arr 中找到并通过分配给 expressionmain() 中可用的标记 -- 传递 A指向 n_tokens 的指针作为第二个参数,并在 return arr; 之前将其更新为 i 的值,例如

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;
}

现在 main() 中的 n_tokens 仅包含实际找到并分配给 arr[i] 的 token 数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) 它可以在不再需要时被释放

您必须使用内存错误检查程序来确保您不会尝试访问内存或写入超出/超出您分配的 block 的边界,尝试读取或基于未初始化的值进行条件跳转,最后, 以确认您释放了所有已分配的内存。

对于 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)

始终确认您已释放所有分配的内存并且没有内存错误。

检查一下,如果您还有其他问题,请告诉我。

关于c - 将字符串拆分为标记并将标记放入数组 - strtok,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53246140/

相关文章:

C 风格的 printf/scanf

c++ - 将数组分成 k 个连续的分区,使得最大分区的总和最小

c++ - 多阵列动态内存分配错误

java - 在Java中直接将存储为字符串的十六进制数字写入十六进制文件?

c 中的字符传递参数

在文本段中创建具有给定大小的 C 函数

c - 从Linux内核解释notifier.c

mysql - 为网站存储数据数组的最佳方法?

java - SoundBoard 将音频输出更改为虚拟音频线

vb.net - 如何与流程输出交互?