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

时间:2018-06-28 02:31:41

标签: c strtok

我曾经考虑过第一次调用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,不是吗?

3 个答案:

答案 0 :(得分:1)

根据功能documentation,该功能的正确用法 是您提到的第一个。

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

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

  2.   
  3. 序列中的第一个调用具有一个非空的第一个参数,并且s1max指向一个对象   其值是第一个指向的字符数组中的元素数   论点。第一次调用将初始值存储在ptr和   更新s1max指向的值以反映保留在其中的元素数量   与ptr的关系。 序列中的后续调用的第一个参数为空,并且   要求s1max和ptr指向的对象必须具有由   序列中的上一个呼叫,然后进行更新。指向的分隔符字符串   s2与通话之间可能有所不同。

  4.   

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

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

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

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

答案 1 :(得分:1)

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

不,这是不好的做法。

即使它确实起作用(如此处所示),也是很糟糕的,因为您不得不问这个问题。您之所以要问这个问题,是因为该代码看起来令人惊讶,这意味着该代码的作者由于使其变得比原来更难理解而浪费了您的时间。

  

...它可能面临的潜在错误是什么?

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

由于这不是一种好习惯,因此这个问题尚无定论,但值得指出的另一个原因与您的前任显然未能遵循的最小惊讶原则有关: consistency 。 / p>

一致的接口不太令人惊讶,更易于推理并且更容易避免混乱。该原型保留了与其他现有接口的一致性(尽管我看到您使用的是MS strtok_s而不是标准的C11版本)-如果删除第一个参数,则比较源字符串和定界符参数的明显顺序到其他strtok函数。

答案 2 :(得分:1)

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

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