c-总是将strtok_s的第一个参数保持为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 );

我知道它的工作原理和工作方式。因为CCD_ 3指向与CCD_ 4相同的缓冲器。但我觉得有点奇怪,我的怀疑是:

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

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

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

  1. 对strtok_s函数的一系列调用将s1指向的字符串分解为令牌序列,每个令牌由指向的字符串中的一个字符分隔按s2第四个参数指向调用者提供的char指针strtoks函数存储继续扫描所需的信息字符串

  2. 序列中的第一个调用有一个非null的第一个参数并且s1max指向一个对象其值是第一个所指向的字符数组中的元素数论点第一个调用在ptr和更新s1max指向的值,以反映保留在中的元素数与ptr的关系序列中的后续调用的第一个参数为nulls1max和ptr指向的对象需要具有由序列中的上一个调用,然后更新。指向的分隔符字符串s2可能因呼叫而异。

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

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

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

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

  1. 这是使用strtok_s()的好方法吗

不,这是不好的做法。

即使它碰巧起作用(就像这里一样),也很糟糕,因为你必须问这个问题。你之所以问这个问题,是因为代码看起来令人惊讶,这意味着这个代码的作者浪费了你的时间,让它变得更难理解。

。。。它可能面临哪些潜在缺陷?

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

由于这不是一个好的实践,这个问题是没有意义的,但值得指出另一个原因,与你的前任明显没有遵守的最小惊喜原则有关:一致性

一致的接口不那么令人惊讶,更容易推理,也更容易避免混乱。此原型与其他现有接口保持一致(尽管我看到您使用的是MSstrtok_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;
}

最新更新