标记字符串 - C

Tokenizing a String - C

我正在尝试根据 \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

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

strtok 存储 "the point where the last token was found" :

"The point where the last token was found is kept internally by the function to be used on the next call (particular library implementations are not required to avoid data races)." -- 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, which stores the state externally (like in this answer).

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

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

对我来说突出的一件事是,除非您正在对字符串缓冲区执行其他操作,否则无需将每个标记复制到其自己的缓冲区。 strtok 函数 returns 指向令牌开头的指针,因此您可以就地使用令牌。下面的代码可能效果更好,也更容易理解:

#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 returns 指向缓冲区内某个点的指针

如果您确实在对数据做其他事情并且确实需要缓冲区来做其他事情,那么下面的代码也可以工作。这使用 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] = '[=11=]';
        printf("%s\n", ptr[i]);
      }
      else ptr[i] = NULL;
  }

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

不要忘记 free() 数组中的指针。

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