为什么 strtok 会在 return 字符串中留下部分分隔符?
Why would strtok leave part of a delimiter in the return string?
int parse_request(const char *request, char *method,
char *hostname, char *port, char *uri) {
if (!is_complete_request(request)) {
return 0;
}
char reqCpy[strlen(request)];
strcpy(reqCpy, request);
strcpy(method, strtok(reqCpy, " "));
strtok(NULL, "//");
char* url = strtok(NULL, " ");
printf("URL is %s\n", url);
if (strstr(url, ":") == NULL) {
char newCpy[strlen(request)];
strcpy(newCpy, request);
strtok(newCpy, "//");
char* name = strtok(NULL, "/");
strcpy(hostname, name);
strcpy(port, "80");
} else {
char newCpy[strlen(request)];
strcpy(newCpy, request);
strtok(newCpy, "//");
char* name = strtok(NULL, ":");
strcpy(hostname, name);
strcpy(port, strtok(NULL, "/"));
}
//printf("URI: %s", uri);
return 1;
}
req1 = "获取 http://www.example.com/index.html HTTP/1.0\r\n"
“主持人:www.example.com\r\n”
“用户代理:Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0\r\n “
“接受语言:en-US,en;q=0.5\r\n\r\n”;
req2 = "获取 http://www.example.com:8080/index.html?foo=1&bar=2 HTTP/1.0\r\n"
“主持人:www.example.com:8080\r\n"
“用户代理:Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0\r\n “
“接受语言:en-US,en;q=0.5\r\n\r\n”;
导致未定义行为 (UB) 的各种问题包括
char reqCpy[strlen(request)];
strcpy(reqCpy, request);
reqCpy[]
太小,减 1 来存储长度 strlen(request)
的 字符串 。还需要 1 个。
char reqCpy[strlen(request) + 1];
strcpy(reqCpy, request);
至少解决这个问题,否则其余代码功能有问题。
这就是 strtok
的工作方式。它当然不是世界上最方便的界面,但至少它有相当完善的文档记录。
您似乎假设 strtok(…, "//");
使用字符串 "//"
作为分隔符。但是 strtok
的第二个参数不是一个单一的,可能是多字符的定界符。它是一组可能的单个分隔符,其中任何一个或所有字符都将被视为分隔符。有关详细信息,请参阅 strtok manpage。
您会发现网上有很多关于 strtok
的警告,其中很多都是合理的。但是您的代码中的“陷阱”并没有被提及太多,这与您使用不同的分隔符集调用 strtok
两次时发生的情况有关。
回顾一下,strtok
首先跳过定界符,然后跳过非定界符,然后用0覆盖下一个定界符(如果有的话)。它将下一个字符的地址保存为后续调用。
当分隔符集未更改时,这按预期工作。它在没有重复分隔符的字符串上按预期工作。但是当字符串重复定界符并且定界符集为第二个标记更改时,会出现奇怪的情况。意外结果(在您的情况下,是一个神秘的 /
)是由于第一次调用 strtok
仅删除了标记后的第一个定界符。如果令牌后跟一系列分隔符,则下一次调用 strtok
需要跳过该序列的其余部分。当然,如果定界符集没有改变,它就会这样做。但是,如果您更改了第二次调用的分隔符集,那么第一次调用中被视为分隔符的内容在第二次调用中不再被视为分隔符,并且不会被跳过,这可能与预期相反。
我的猜测是 strtok
并不是您要执行的操作的最佳标准库函数。我建议考虑以下组合:
strstr
查找多字符字符串,如 //
.
strchr
查找下一个特定字符。
strpbrk
查找下一次出现的一组字符。
strspn
和 strcspn
求一组字符序列的长度(或一组字符的倒数)。
请注意,前三个函数的 return 值与后两个函数的 return 值不同。前三个函数为您提供指向正在搜索的 substring/character 的指针,如果搜索失败则为 NULL。最后两个函数计算字符数直到集合中的一个成员;如果找不到,他们只会 return 字符串中剩余的字符数。 (这种行为通常更容易处理,这就是将函数添加到 C 标准的原因。)
注意: 您还需要修复由 识别的缓冲区溢出问题。您必须始终意识到需要为字符串的终止 NUL (0) 分配空间。但是,如果您重写代码以避免使用 strtok
,您可能还会发现需要更少的字符串副本,甚至可能 none.
int parse_request(const char *request, char *method,
char *hostname, char *port, char *uri) {
if (!is_complete_request(request)) {
return 0;
}
char reqCpy[strlen(request)];
strcpy(reqCpy, request);
strcpy(method, strtok(reqCpy, " "));
strtok(NULL, "//");
char* url = strtok(NULL, " ");
printf("URL is %s\n", url);
if (strstr(url, ":") == NULL) {
char newCpy[strlen(request)];
strcpy(newCpy, request);
strtok(newCpy, "//");
char* name = strtok(NULL, "/");
strcpy(hostname, name);
strcpy(port, "80");
} else {
char newCpy[strlen(request)];
strcpy(newCpy, request);
strtok(newCpy, "//");
char* name = strtok(NULL, ":");
strcpy(hostname, name);
strcpy(port, strtok(NULL, "/"));
}
//printf("URI: %s", uri);
return 1;
}
req1 = "获取 http://www.example.com/index.html HTTP/1.0\r\n" “主持人:www.example.com\r\n” “用户代理:Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0\r\n “ “接受语言:en-US,en;q=0.5\r\n\r\n”;
req2 = "获取 http://www.example.com:8080/index.html?foo=1&bar=2 HTTP/1.0\r\n" “主持人:www.example.com:8080\r\n" “用户代理:Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0\r\n “ “接受语言:en-US,en;q=0.5\r\n\r\n”;
导致未定义行为 (UB) 的各种问题包括
char reqCpy[strlen(request)];
strcpy(reqCpy, request);
reqCpy[]
太小,减 1 来存储长度 strlen(request)
的 字符串 。还需要 1 个。
char reqCpy[strlen(request) + 1];
strcpy(reqCpy, request);
至少解决这个问题,否则其余代码功能有问题。
这就是 strtok
的工作方式。它当然不是世界上最方便的界面,但至少它有相当完善的文档记录。
您似乎假设 strtok(…, "//");
使用字符串 "//"
作为分隔符。但是 strtok
的第二个参数不是一个单一的,可能是多字符的定界符。它是一组可能的单个分隔符,其中任何一个或所有字符都将被视为分隔符。有关详细信息,请参阅 strtok manpage。
您会发现网上有很多关于 strtok
的警告,其中很多都是合理的。但是您的代码中的“陷阱”并没有被提及太多,这与您使用不同的分隔符集调用 strtok
两次时发生的情况有关。
回顾一下,strtok
首先跳过定界符,然后跳过非定界符,然后用0覆盖下一个定界符(如果有的话)。它将下一个字符的地址保存为后续调用。
当分隔符集未更改时,这按预期工作。它在没有重复分隔符的字符串上按预期工作。但是当字符串重复定界符并且定界符集为第二个标记更改时,会出现奇怪的情况。意外结果(在您的情况下,是一个神秘的 /
)是由于第一次调用 strtok
仅删除了标记后的第一个定界符。如果令牌后跟一系列分隔符,则下一次调用 strtok
需要跳过该序列的其余部分。当然,如果定界符集没有改变,它就会这样做。但是,如果您更改了第二次调用的分隔符集,那么第一次调用中被视为分隔符的内容在第二次调用中不再被视为分隔符,并且不会被跳过,这可能与预期相反。
我的猜测是 strtok
并不是您要执行的操作的最佳标准库函数。我建议考虑以下组合:
strstr
查找多字符字符串,如//
.strchr
查找下一个特定字符。strpbrk
查找下一次出现的一组字符。strspn
和strcspn
求一组字符序列的长度(或一组字符的倒数)。
请注意,前三个函数的 return 值与后两个函数的 return 值不同。前三个函数为您提供指向正在搜索的 substring/character 的指针,如果搜索失败则为 NULL。最后两个函数计算字符数直到集合中的一个成员;如果找不到,他们只会 return 字符串中剩余的字符数。 (这种行为通常更容易处理,这就是将函数添加到 C 标准的原因。)
注意: 您还需要修复由 strtok
,您可能还会发现需要更少的字符串副本,甚至可能 none.