我正在尝试基于\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
我不确定为什么我看不到另一个令牌,因为程序结束了,这意味着循环必须发生到整个字符串的结尾,对吧?
答案 0 :(得分:4)
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);
}
答案 1 :(得分:0)
strtok
将最后一个标记的状态存储在全局变量中,以便下一次调用strtok
知道继续的位置。因此,当您在strtok(buf2, "/");
中调用if
时,它会破坏有关外部标记化的已保存状态。
修复方法是使用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;
}
答案 2 :(得分:0)
有一点让我感到惊讶的是,除非你用字符串缓冲区做其他事情,否则不需要将每个标记复制到它自己的缓冲区。 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。
当你完成它们时,不要忘记释放数组中的指针。
在你的代码中,你将东西放在命名缓冲区中,这可以完成,但它并不是一个很好的实践,因为如果你试图在其他地方使用代码,你必须对其进行大量修改