【问题标题】:split a char array into tokens where the separator is NUL char将 char 数组拆分为分隔符为 NUL char 的标记
【发布时间】:2023-04-10 16:23:02
【问题描述】:

我想使用NUL char 作为分隔符将一个 char 数组拆分为标记。

我有一个通过网络从recv 命令收到的 char 数组,所以我知道 char 数组的长度。在该 char 数组中,有一堆字符串由 NUL 字符 (\0) 分隔。

因为分隔符是NUL 字符,这意味着我不能使用strtok,因为它使用NULL 用于自己的目的。

所以我想遍历从字节 8 开始的所有字符串(字符串前面有 2 个 32 位整数)。

我想我可以遍历所有字符来寻找 \0 字符,然后执行我目前找到的长度的 memcpy,但我想一定有比这更好的方法。

我还能采取什么其他方法?

【问题讨论】:

  • 如果您不需要将缓冲区重新用于其他用途,您可以将字符串存储在那里,只需使用指向它们开头的指针。它们已经是 NUL 终止的。
  • @ThomasPadron-McCarthy 好久没做c了,怎么再次移动指针的位置?
  • 或者如果你确实需要重新使用缓冲区,你可以找到每个字符串的第一个字符和strcpy()或strdup()它。无论哪种方式,请确保最后一个字符串也以 null 结尾,或者将其作为特殊情况处理。
  • "如何移动指针的位置 [...]?"你没有。指针是一个值——它就是它的本来面目。但是您可以通过指针算法计算一个指向所需位置的 new 指针值。如果您愿意,您可以将新指针值存储在最初保存原始指针的相同变量 中。如果这对您来说还没有意义,那么是时候回顾一下您的 C 语言基础知识了。
  • NULL 和 NUL 不一样。

标签: c string split nul


【解决方案1】:

下面是一些简单的代码,展示了如何获取包含的字符串:

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

int main(void) {
    char recbuf[7] = {'a', 'b', 'c', '\0', 'd', 'e', '\0'};
    int recbuf_size = 7;
    int j = 0;
    char* p = recbuf;
    while(j < recbuf_size) 
    {
        printf("%s\n", p);  // print the string found
                            // Here you could copy the string if needed, e.g.
                            // strcpy(mySavedStrings[stringCount++], p);

        int t = strlen(p);  // get the length of the string just printed
        p += t + 1;         // move to next string - add 1 to include string termination
        j += t + 1;         // remember how far we are
    }
    return 0;
}

输出:

abc
de

如果您需要在缓冲区的开头跳过一些字节,那么只需执行以下操作:

int number_of_bytes_to_skip = 4;
int j = number_of_bytes_to_skip;
char* p = recbuf + number_of_bytes_to_skip;

注意:

上面的代码假定接收缓冲区总是以'\0' 正确终止。在实际代码中,您应该在运行代码之前检查并添加错误处理,例如:

if (recbuf[recbuf_size-1] != '\0')
{
    // Some error handling...
}

【讨论】:

  • 我将recbuf 重新用于recv 的多次调用。这是否意味着我需要将指针重置回开头才能使用这种方法?如果我只是在遍历它之前复制recbuf,那么原始指针应该没问题?
  • @JoelPearson - 每当您收到新缓冲区时,您需要将指针 p 设置为新缓冲区的地址(并添加要跳过的字节数)。如果您重用相同的接收缓冲区,那么您需要在解析缓冲区之前将指针 p 设置回来
  • 刚刚试用了一下,非常适合我的需要,非常感谢!
【解决方案2】:

NUL 分离实际上让你的工作变得轻松很多。

char* DestStrings[MAX_STRINGS];
int j = 0;
int length = 0; 
inr prevLength =0;
int offset = 8;
for(int i = 0;i<MAX_STRINGS;i++) 
{
     length += strlen(&srcbuffer[j+offset+length]); 
     if(length == prevLength)                          
     { 
       break;
     }
     else
     {

       DestStrings[i] = malloc(length-prevLength+1);
       strcpy(DestStrings[i],&srcbuffer[j+offset+length]);
       prevLength = length;
       j++;
     }

}

您需要添加一些额外的检查以避免潜在的缓冲区溢出错误。 希望这段代码能给你一些关于如何继续的想法。

编辑 1: 尽管由于修改索引的反对票,这是从不是整个解决方案开始的代码。

编辑 2: 由于接收数据缓冲区的长度是已知的,请在接收数据后附加 NUL 以使此代码按原样工作。另一方面,接收数据的长度本身可以用来与复制的长度进行比较。

【讨论】:

    【解决方案3】:

    我建议使用实现标记器的结构来完成此类工作。它会更容易阅读和维护,因为它看起来类似于面向对象的代码。它隔离了 memcpy,所以我认为它“更好”。

    首先,我将使用的标题:

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

    Tokenizer 结构必须记住字符串的开头(这样我们可以在不再需要内存后擦除内存)、实际索引和结束索引,以检查我们是否已经解析了整个字符串:

    struct Tokenizer {
        char *string;
        char *actual_index;
        char *end_index;
    };
    

    我建议使用类似工厂的函数来创建分词器。它在这里构造,使用 memcpy 复制输入字符串,因为 string.h 函数在第一个 '\0' 字符处停止。

    struct Tokenizer getTokenizer(char string[], unsigned length) {
        struct Tokenizer tokenizer;
        tokenizer.string = (char *)malloc(length);
        tokenizer.actual_index = tokenizer.string;
        tokenizer.end_index = tokenizer.string + length;
        memcpy(tokenizer.string, string, length); 
        return tokenizer;
    }
    

    现在是负责获取令牌的函数。它返回新分配的字符串,其末尾有一个 '\0' 字符。它还更改了 actual_index 指向的地址。它将分词器的地址作为参数,因此可以更改其值:

    char * getNextToken(struct Tokenizer *tokenizer) {
        char * token;
        unsigned length;
        if(tokenizer->actual_index == tokenizer->end_index) 
            return NULL;
        length = strlen(tokenizer->actual_index);
        token = (char *)malloc(length + 1); 
        // + 1 because the '\0' character has to fit in
        strncpy(token, tokenizer->actual_index, length + 1);
        for(;*tokenizer->actual_index != '\0'; tokenizer->actual_index++) 
            ; // getting the next position
        tokenizer->actual_index++;
        return token;
    }
    

    分词器的使用示例,展示如何处理内存分配以及如何使用它。

    int main() {
        char c[] = "Lorem\0ipsum dolor sit amet,\0consectetur"
            " adipiscing elit. Ut\0rhoncus volutpat viverra.";
        char *temp;
        struct Tokenizer tokenizer = getTokenizer(c, sizeof(c));
        while((temp = getNextToken(&tokenizer))) {
            puts(temp);
            free(temp);
        }
        free(tokenizer.string);
        return 0;
    }
    

    【讨论】:

    • 我来自 Java 背景,所以这种方式对我来说确实更好看,谢谢。
    【解决方案4】:

    假设这个输入数据:

    char input[] = {
      0x01, 0x02, 0x0a, 0x0b,  /* A 32bit integer */
      'h', 'e', 'l', 'l', 'o', 0x00, 
      'w', 'o', 'r', 'l', 'd', 0x00,
      0x00 /* Necessary to make the end of the payload. */
    };
    

    开头的 32 整数给出:

    const size_t header_size = sizeof (uint32_t);
    

    解析输入可以通过识别“字符串”的第一个字符并存储一个指向它的指针来完成,然后在找到的字符串长(1+)处继续前进,然后重新开始直到输入结束已到达。

    size_t strings_elements = 1; /* Set this to which ever start size you like. */
    size_t delta = 1; /* 1 is conservative and slow for larger input, 
                         increase as needed. */
    
    /* Result as array of pointers to "string": */
    char ** strings = malloc(strings_elements * sizeof *strings);
    
    {  
      char * pc = input + header_size;
      size_t strings_found = 0;
      /* Parse input, if necessary increase result array, and populate its elements: */
      while ('\0' != *pc)
      {
        if (strings_found >= strings_elements)
        {
          strings_elements += delta;
          void * pvtmp = realloc(
            strings, 
            (strings_elements + 1) * sizeof *strings /* Allocate one more to have a 
                                            stopper, being set to NULL as a sentinel.*/
          ); 
    
          if (NULL == pvtmp)
          {
            perror("realloc() failed");
            exit(EXIT_FAILURE);
          }
    
          strings = pvtmp;
        }
    
        strings[strings_found] = pc; 
        ++strings_found;
    
        pc += strlen(pc) + 1;
      }
    
      strings[strings_found] = NULL; /* Set a stopper element. 
                                        NULL terminate the pointer array. */
    }
    
    /* Print result: */
    {
      char ** ppc = strings;
      for(; NULL != *ppc; ++ppc)
      {
        printf("%zu: '%s'\n", ppc - strings + 1, *ppc)
      }
    }
    
    /* Clean up: */
    free(strings);
    

    如果需要在拆分时复制,请替换此行

      strings[strings_found] = pc; 
    

    由

      strings[strings_found] = strdup(pc); 
    

    并在使用free()ing strings之前和之后添加清理代码:

    {
      char ** ppc = strings;
      for(; NULL != *ppc; ++ppc)
      {
        free(*ppc);
      }
    }
    

    上面的代码假定负载后至少有 1 个'\0'(NUL 又名空字符)。

    如果不满足后一个条件,您需要定义/围绕任何其他终止序列,或者需要知道来自其他来源的输入的大小。如果您不这样做,您的问题将无法解决。

    上面的代码需要以下头文件:

    #include <inttypes.h> /* for int32_t */
    #include <stdio.h> /* for printf(), perror() */
    #include <string.h>  /* for strlen() */
    #include <stdlib.h> /* for realloc(), free(), exit() */
    

    它可能需要以下定义之一:

    #define _POSIX_C_SOURCE 200809L
    
    #define _GNU_SOURCE
    

    或者您的 C 编译器还需要什么才能使 strdup() 可用。

    【讨论】:

      猜你喜欢
      • 2012-02-25
      • 2017-10-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-20
      • 2021-12-06
      • 2013-02-23
      相关资源
      最近更新 更多