繁体   English   中英

始终将 strtok_s 的第一个参数保持为 NULL 是否正确?

[英]Is it right to always keep the first parameter of strtok_s as NULL?

我曾经考虑过什么时候第一次调用strtok_s()应该传递包含令牌的字符串作为第一个参数,如下代码:

char testString[100] = "1|2|3";
char *context = testString;
const char *token = strtok_s( testString, "|", &context );
while ( token )
    token = strtok_s( NULL, "|", &context );

但是,我看到有人总是将第一个参数保留为NULL ,如下代码:

char testString[100] = "1|2|3";
char *context = testString;
const char *token = strtok_s( NULL, "|", &context );
while ( token )
    token = strtok_s( NULL, "|", &context );

我知道它的工作原理以及它是如何工作的。 因为context指向与testString相同的缓冲区。 但我觉得有点奇怪,我的疑问是:

  1. 使用strtok_s()是一个好习惯吗? 它可能面临哪些潜在的错误?
  2. 如果这是一个好习惯,为什么strtok_s()仍然需要保留第一个参数? 它可以像往常一样为NULL ,不是吗?

根据函数文档,该函数的正确用法是您提到的第一个。

进一步引用 C11 标准(强调我的),第 K.3.7.3.1 节(第 616 页):

  1. 对 strtok_s 函数的调用序列将 s1 指向的字符串分解为一系列标记,每个标记都由 s2 指向的字符串中的一个字符分隔。 第四个参数指向调用者提供的 char 指针,strtok_s 函数在其中存储它继续扫描同一个 string 所需的信息

  2. 序列中的第一个调用有一个非空的第一个参数,s1max 指向一个对象,该对象的值是第一个参数指向的字符数组中的元素数。 第一次调用在 ptr 指向的对象中存储一个初始值,并更新 s1max 指向的值以反映与 ptr 相关的剩余元素数。 序列中的后续调用的第一个参数为 null,并且 s1max 和 ptr 指向的对象需要具有序列中前一个调用存储的值,然后更新这些值 s2 指向的分隔符字符串可能与调用不同。

因此,标准所说的正​​确用法是使用非 NULL 的第一个参数调用strtok_s ,然后使用 NULL 的第一个参数调用它。 在第一次调用时,该函数初始化一些状态,并使用提供的指针(最后一个参数)来存储它。

该标准没有提到应该如何使用最后一个参数,而不是保持状态,以便在使用未修改的指针调用时函数可以继续搜索相同的字符串。 基本上,它不需要strtok的内部状态,例如,您可以同时标记多个字符串。

因此,状态空间的使用方式是实现定义的 很可能是这样,在某些实现中,它可以简单地将初始字符串放在那里,并始终使用第一个参数 NULL 调用它,如您所示。 但是不能保证这会发生在所有实现上,或者这种行为在库的未来版本中将保持不变。

直接回答您的问题,是的,它可能有效,但不,这样做不是一个好主意。

  1. 使用strtok_s()是一个好习惯吗?

不,这是不好的做法。

即使它碰巧起作用(就像这里一样),这也很糟糕,因为您必须提出问题。 你问这个问题是因为代码看起来很奇怪,这意味着这段代码的作者浪费了你的时间,让它更难理解。

... 它可能面临哪些潜在的错误?

  • 另一个 C 库可能会以不同的方式实现该功能
  • 另一个开发人员可能会移动或更改context的分配,因为像这样使用它是不寻常的
  1. 如果这是一个好习惯,为什么 strtok_s() 仍然需要保留第一个参数?

由于这不是一个好的做法,这个问题没有实际意义,但值得指出另一个原因,与您的前任如此明显未能遵守的最小惊喜原则相关:一致性

一致的接口不那么令人惊讶,更容易推理并且更容易避免混乱。 这个原型保持与其他现有接口的一致性(尽管我看到您使用的是 MS strtok_s而不是标准的 C11 版本) - 如果您删除第一个参数,与其他strtok相比,源字符串和分隔符参数的明显顺序是相反的职能。

我个人认为第二种形式是无害的。 因为:

  1. 许多代码已经以第二种形式编写。 更改实现会破坏它们并造成不必要的麻烦。
  2. 没有更好的实施空间。

我什至只在代码中使用 strtok_s() 一次:

char* context = testString;
// 1. with 'for' loop: 'token' will not leak into outer scope
for (const char* token; (token = strtok_s(NULL, "|", &context)) != NULL;)
    use(token);

// 2. with 'while' loop
char* context = testString;
const char* token2;
while((token2 = strtok_s(NULL, "|", &context)) != NULL)
    use(token);

下面是我的团队一直在使用的一个实现。

char* my_strtok_s(char* buf, const char* splitters, char** context)
{
    char* p = *context;
    char* token;

    if (buf != NULL)
        p = buf;

    if (p == NULL) 
    {
        *context = p;
        return NULL;
    }

    while(strchr(splitters, *p) && *p != 0)
        p++;

    token = p;
    while(*p != 0)
    {
        if (strchr(splitters, *p))
        {
            *p = 0;
            p++;
            break;
        }
        p++;
    }

    *context = p;

    return *token != 0 ? token : NULL;
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM