【问题标题】:Tokenizing a String - C标记字符串 - C
【发布时间】:2015-04-28 02:44:48
【问题描述】:

我正在尝试根据 \r\n 分隔符在 C 中标记一个字符串,并希望在后续调用 strtok() 后打印出每个字符串。在我拥有的while 循环中,对每个令牌都进行了处理。

当我包含处理代码时,我收到的唯一输出是第一个令牌,但是当我取出处理代码时,我收到了每个令牌。这对我来说没有意义,我想知道我做错了什么。

代码如下:

#include <stdio.h>
#include <time.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
        int c = 0, c2 = 0;
        char *tk, *tk2, *tk3, *tk4;
        char buf[1024], buf2[1024], buf3[1024];
        char host[1024], path[1024], file[1024];

        strcpy(buf, "GET /~yourloginid/index.htm HTTP/1.1\r\nHost: remote.cba.csuohio.edu\r\n\r\n");

        tk = strtok(buf, "\r\n");
        while(tk != NULL)
        {
                printf("%s\n", tk);
                /*
                if(c == 0)
                {
                        strcpy(buf2, tk);
                        tk2 = strtok(buf2, "/");
                        while(tk2 != NULL)
                        {
                                if(c2 == 1)
                                        strcpy(path, tk2);
                                else if(c2 == 2)
                                {
                                        tk3 = strtok(tk2, " ");
                                        strcpy(file, tk3);
                                }
                                ++c2;
                                tk2 = strtok(NULL, "/");
                        }
                }
                else if(c == 1)
                {
                        tk3 = strtok(tk, " ");
                        while(tk3 != NULL)
                        {
                                if(c2 == 1)
                                {
                                        printf("%s\n", tk3);
                                //      strcpy(host, tk2);
                                //      printf("%s\n", host);
                                }
                                ++c2;
                                tk3 = strtok(NULL, " ");
                        }
                }
                */
                ++c;
                tk = strtok(NULL, "\r\n");
        }

        return 0;
}

如果没有那些 if else 语句,我会收到以下输出...

GET /~yourloginid/index.htm HTTP/1.1
Host: remote.cba.csuohio.edu

...但是,通过这些 if else 声明,我收到了这个...

GET /~yourloginid/index.htm HTTP/1.1

不知道为什么我看不到另一个token,因为程序结束了,这意味着循环必须一直发生到整个字符串的结尾,对吧?

【问题讨论】:

  • 考虑strtok 必须保留外部枚举的位置上下文。提示:check the docs。现在考虑那些 inner strtok 调用对隐藏的上下文做了什么。 strtok_s 或 POSIX strtok_r 之类的替代方案可能会减轻您的麻烦。

标签: c tokenize


【解决方案1】:

strtok 存储“找到最后一个令牌的点”

“找到最后一个标记的点由函数内部保存,以便在下次调用时使用(不需要特定的库实现来避免数据竞争)。” -- reference

这就是为什么你可以用 NULL 第二次调用它。

因此,您在循环中使用不同的指针再次调用它会使您失去初始调用的状态(这意味着 tk = strtok(NULL, "\r\n") 将在 while 结束时为 NULL,因为它将使用内部循环的状态)。

所以解决办法大概是把while的最后一行改成:

tk = strtok(NULL, "\r\n");

到类似的东西(请先检查边界,它不应该在buf + strlen(buf)之后):

tk = strtok(tk + strlen(tk) + 1, "\r\n");

或者使用strtok_r,它将状态存储在外部(就像这个answer)。

// first call
char *saveptr1;
tk = strtok_r(buf, "\r\n", &saveptr1);
while(tk != NULL) {
   //...
   tk = strtok_r(NULL, "\r\n", &saveptr1);
}

【讨论】:

    【解决方案2】:

    strtok 将最后一个标记的状态存储在一个全局变量中,以便下一次调用strtok 知道从哪里继续。因此,当您在 if 中调用 strtok(buf2, "/"); 时,它会破坏有关外部标记化的已保存状态。

    解决方法是使用strtok_r 而不是strtok。这个函数需要一个额外的参数来存储状态:

    char *save1, *save2, *save3;
    
    tk = strtok_r(buf, "\r\n", &save1);
        while(tk != NULL) {
            printf("%s\n", tk);
            if(c == 0) {
                strcpy(buf2, tk);
                tk2 = strtok_r(buf2, "/", &save2);
                while(tk2 != NULL) {
                    if(c2 == 1)
                        strcpy(path, tk2);
                    else if(c2 == 2) {
                        tk3 = strtok_r(tk2, " ", &save3);
                        strcpy(file, tk3); }
                    ++c2;
                    tk2 = strtok_r(NULL, "/", &save2); }
            } else if(c == 1) {
                tk3 = strtok_r(tk, " ", &save2);
                while(tk3 != NULL) {
                    if(c2 == 1) {
                        printf("%s\n", tk3);
                    //  strcpy(host, tk2);
                    //  printf("%s\n", host);
                    }
                    ++c2;
                    tk3 = strtok_r(NULL, " ", &save2); } }
            ++c;
            tk = strtok_r(NULL, "\r\n", &save1); }
        return 0;
    }
    

    【讨论】:

      【解决方案3】:

      让我印象深刻的一件事是,除非您对字符串缓冲区执行其他操作,否则无需将每个标记复制到其自己的缓冲区。 strtok 函数返回一个指向标记开头的指针,因此您可以就地使用标记。下面的代码可能会更好,更容易理解:

      #define MAX_PTR = 4
      
      char buff[] = "GET /~yourloginid/index.htm HTTP/1.1\r\nHost: remote.cba.csuohio.edu\r\n\r\n";
      char *ptr[MAX_PTR];
      int i;
      
      for (i = 0; i < MAX_PTR; i++)
        {
          if (i == 0) ptr[i] = strtok(buff, "\r\n");
            else ptr[i] = strtok(NULL, "\r\n");
          if (ptr[i] != NULL) printf("%s\n", ptr[i]);
        }
      

      我定义缓冲区的方式称为预加载缓冲区。您可以使用设置为等于字符串的数组来初始化数组。编译器将为您调整大小,而您无需执行任何其他操作。现在在 for 循环中,if 语句确定使用哪种形式的 strtok。所以如果i == 0,那么我们需要初始化strtok。否则,我们对所有后续标记使用第二种形式。然后 printf 只打印不同的标记。请记住,strtok 返回指向缓冲区内某个点的指针

      如果您确实在对数据做其他事情并且您确实需要缓冲区来做其他事情,那么下面的代码也可以工作。这使用 malloc 从堆中分配内存块。

      #define MAX_PTR = 4
      
      char buff[] = "GET /~yourloginid/index.htm HTTP/1.1\r\nHost: remote.cba.csuohio.edu\r\n\r\n";
      char *ptr[MAX_PTR];
      char *bptr;  /* buffer pointer */
      int i;
      
      for (i = 0; i < MAX_PTR; i++)
        {
          if (i == 0) bptr = strtok(buff, "\r\n");
            else bptr = strtok(NULL, "\r\n");
          if (bptr != NULL)
            { 
              ptr[i] = malloc(strlen(bptr + 2));
              if (ptr[i] == NULL)
                {
                  /* Malloc error check failed, exit program */
                  printf("Error: Memory Allocation Failed. i=%d\n", i);
                  exit(1);
                }
              strncpy(ptr[i], bptr, strlen(bptr) + 1);
              ptr[i][strlen(bptr) + 1] = '\0';
              printf("%s\n", ptr[i]);
            }
            else ptr[i] = NULL;
        }
      

      除了我们将标记字符串复制到缓冲区之外,这段代码的作用几乎相同。请注意,我们使用 char 指针数组来执行此操作。 malloc 调用分配内存。然后我们检查它是否失败。如果 malloc 返回 NULL,那么它失败并且我们退出程序。应该使用 strncpy 函数而不是 strcpy。 strcpy 不允许检查目标缓冲区的大小,因此恶意用户可以对您的代码执行缓冲区溢出攻击。 malloc 被赋予 strlen(bptr) + 2。这是为了保证缓冲区的大小足以处理令牌的大小。 strlen(bptr) + 1 表达式是为了确保复制的数据不会超出缓冲区。作为额外的预防措施,缓冲区中的最后一个字节设置为 0x00。然后我们打印字符串。请注意,我有 if (bptr != NULL)。所以主代码块只有在strtok返回一个指向有效字符串的指针时才会执行,否则我们将数组中对应的指针入口设置为NULL。

      完成后不要忘记释放数组中的指针。

      在您的代码中,您将内容放在命名缓冲区中,这是可以做到的,但这并不是一个很好的做法,因为如果您尝试在其他地方使用该代码,则必须对其进行大量修改。

      【讨论】:

        猜你喜欢
        • 2012-04-20
        • 1970-01-01
        • 1970-01-01
        • 2010-09-20
        • 2011-05-31
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多