使用 strtok 多次拆分一个字符串会导致意外行为

using strtok to split one string more than once leads to an unexpected behavior

提问人:Davood 提问时间:9/18/2023 最后编辑:Davood 更新时间:9/19/2023 访问量:99

问:

我遇到了一个问题,我需要有人来解释发生了什么。

我正在解析一个字符串,我想将其解析为,然后拆分每个.a=1&b=2&c=3["a=1","b=2","c=3"]x=y

代码如下:


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

void f2(char *src)
{
    char *dest = (char *) calloc(strlen(src)+1,sizeof(char));    
   
    strcpy(dest,src); // I copy src to dest to guard src from being messed up with strtok

   // when I comment out the below line, src address doesn't change
   // but why is it changing the src address? I have copied the src to dest!
    char *token = strtok(dest, "="); 
    printf("dest addr: %p token addr: %p \n",dest,token);
}
void f1(char *src)
{
    char *token = strtok(src, "&");
    while (token)
    {
        printf("src addr: %p ", token);
        f2(token);
        token = strtok(NULL, "&");
    }
} 

我运行的代码如下:

TEST(CopyPointer, CopyStrTok)
{
    char str[]="a=1&b=2&c=3";
    f1(str);
}

结果如下:

src addr: 0x7ffd4a00ec0c dest addr: 0x558a755d3350 token addr: 0x558a755d3350 // it's fine 
src addr: 0x558a755d3352 dest addr: 0x558a755d3370 token addr: 0x558a755d3370 
//               ^                         ^    
// now src addr is changed and it's pointing to the second character of dest

我无法解释为什么 在我将 复制到另一个名为 ?srcf2srcdest

校正:

正如其中一个答案中提到的,地址没有改变,只是令牌地址改变了!src

C 指针 strtok

评论

2赞 yano 9/18/2023
这应该是为 NUL 终结者创造空间。而且没有必要投射这个结果char *dest = calloc(strlen(src) + 1,sizeof(char));
1赞 Retired Ninja 9/18/2023
当你在函数内部使用时,它不再与 关联,所以在循环内部是用来查找下一个令牌的。strtok(dest, "=");srctoken = strtok(NULL, "&");dest
6赞 Barmar 9/18/2023
如果要同时执行多个循环,则需要使用 。strtok()strtok_r()
2赞 Fe2O3 9/18/2023
您可以简单地用作分隔符字符串并一次收集 2 个令牌......"&="
1赞 Lundin 9/18/2023
只需在标记字符串并将结果存储在指针数组中时使用即可。strdup

答:

0赞 0___________ 9/18/2023 #1

src没有更改,并且您会看到更改,因为您不仅打印(但在您的格式中,字符串是 src 而不是令牌)srctoken

更正版本

void f1(char *src)
{
    char *token = strtok(src, "&");
    while (token)
    {
        printf("src addr: %p token addr: %p ", (void *)src, (void*)token);
        f2(token);
        token = strtok(NULL, "&");
    }
} 
3赞 dbush 9/18/2023 #2

该函数使用静态内部数据来跟踪它的位置。strtok

因此,当您调用 in 时,它与 in 该函数相关联(这与在测试函数中相同),但是当您再次调用它时,它与 with 作为第一个参数相关联,它现在与 in 相关联。然后,当您再次调用 with 作为第一个参数时,它使用指向其成员的内部指针,该成员不再在作用域内。这会触发未定义的行为strtokf1srcstrf2destdestf2strtokf1NULLdest

如果要使用多个级别的 ,则应改用 允许用户传入附加参数来存储其状态。strtokstrtok_r

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

void f2(char *src)
{
    char *p = NULL;
    char *token = strtok_r(src, "=", &p);
    printf("token a=%s\n", token);
    token = strtok_r(NULL, "=", &p);
    printf("token b=%s\n", token);
}
void f1(char *src)
{
    char *p = NULL;
    char *token = strtok_r(src, "&", &p);
    while (token)
    {
        f2(token);
        token = strtok_r(NULL, "&", &p);
    }
}

int main()
{
    char str[] = "a=1&b=2&c=3";
    f1(str);
    return 0;
}

输出:

token a=a
token b=1
token a=b
token b=2
token a=c
token b=3

评论

1赞 Davood 9/18/2023
很好的解释,现在我明白了,第二个 strtok(NULL,“&”) 仍然指的是 f2 中的 dest。
1赞 Fe2O3 9/18/2023 #3

为什么要让它变得复杂?目标是提取字符串对

int main( void ) {
    char str[] = "a=1&b=2&c=3";

    char *p1, *p2;
    for( p1 = str; ( p1 = strtok( p1, "&=") ) != NULL; p1 = NULL ) {
        p2 = strtok( NULL, "&=" );

        printf( "%s ... %s\n", p1, p2 );
    }
    return 0;
}

输出:

a ... 1
b ... 2
c ... 3