为什么strtof和strtod函数的endptr参数是一个非const char指针类型的指针?

24

标准 C 库函数 strtofstrtod 的签名如下:

float strtof(const char *str, char **endptr);
double strtod(const char *str, char **endptr); 

它们每个函数都将输入字符串str分解为三个部分:

  1. 一个可能为空的初始空白序列
  2. 表示浮点值的字符“主题序列”
  3. 一系列未被识别的字符(不影响转换)。

如果endptr不为NULL,则将*endptr设置为指向转换中最后一个字符之后的字符的指针(换句话说,是尾随序列的开头)。

我想知道:endptr为什么是指向非const char指针?*endptr不是指向const char字符串(即输入字符串str)的指针吗?


这基本上是与strchr和其它类似的问题,只不过这里我们有一个输出参数指针而不是返回值。 - Steve Jessop
@Steve:是的,但它比strchr更棘手,因为你不能在没有显式转换的情况下将const限定指针的地址传递给这些函数。 - R.. GitHub STOP HELPING ICE
3
有趣的问题。基本上这意味着你可以在 strtoX 函数中隐藏从 char const*char* 的类型转换。很奇怪。 - Jens Gustedt
2
@Jens:很烦人,但在C中是不可避免的,因为没有函数重载。 在C++中,有两个strchr函数,如果输入是“char *”,则返回“char *”,如果输入是“const char *”,则返回“const char *”。尽管在C++标准中没有对strtod进行这样的重载,但我不知道背后的理由。在任何人询问之前,C++中根本没有strtof,因为它不在C89中。搜索显示,我长期以来一直无知于此理由:https://dev59.com/BHNA5IYBdhLWcg3wWsiK - Steve Jessop
2个回答

11
原因很简单,为了易用性。char * 可以自动转换为 const char *,但是 char ** 不能自动转换为 const char **,而且调用函数使用的指针的实际类型(其地址被传递)更有可能是 char * 而不是 const char *。这种自动转换不可能发生的原因是,它可以通过多个步骤在不明显地情况下移除 const 限定符,每个步骤本身看起来都是完全有效和正确的。Steve Jessop 在评论中提供了一个例子:

如果你能自动将 char** 转换为 const char**,那么你就可以执行:

char *p;
char **pp = &p;
const char** cp = pp;
*cp = (const char*) "hello";
*p = 'j';.
为了保持常量安全性,其中一行必须是非法的,而且由于其他所有操作都非常正常,因此必须是cp = pp;。 更好的方法是将这些函数定义为接受void *而不是char **char **const char **都可以自动转换为void *(被删除的文字实际上是一个非常糟糕的想法;它不仅防止任何类型检查,而且C实际上禁止类型为char *const char *的对象别名)。或者,这些函数可以接受一个ptrdiff_t *size_t *参数来存储结尾的偏移量,而不是指向它的指针。这通常更有用。 如果您喜欢后一种方法,请随意编写这样的包装器,以调用您的包装器,以保持其余代码const清晰和无需转换。

2
"无论是 char** 还是 const char** 都可以自动转换为 void*。我明白你的意思,使用 void* 可以让用户传递 const char** 而不需要强制转换。但这也会允许他们传递一系列错误的参数而不需要强制转换。考虑到 C 语言的限制,我认为保留基本类型安全性更好,即使失去了 const 安全性也要如此。" - Steve Jessop
2
如果有人有,请发布。- 如果您可以自动将char **转换为const char **,那么您可以执行char *p; char **pp =&p; const char **cp = pp; * cp =(const char *)“hello”; * p ='j';。为了保证常量安全性,其中一行必须是非法的,而且由于其他所有操作都是完全正常的,因此必须是cp = pp; - Steve Jessop
1
一个偏移量确实解决了const问题,尽管现在已经太晚了,无法在整个库中一致使用这种约定,而且在const被发明的时候可能已经过于破坏性了,我不确定。如果字符串处理函数返回偏移量而不是指针是正常的话,那么strcpy是否总是返回0呢?;-) - Steve Jessop
1
理想情况下,“strcpy”将返回字符串的长度,这是一个非常有用的信息,它在操作的副作用中自动获取(然后抛弃)。:-) - R.. GitHub STOP HELPING ICE
这也意味着你不能这样做:const char *end; strtof(str, &end); 另一方面,由于这个原因,你可以这样做:char *end; strtof(str, &end); 现在,你有一个指向可能是const内存的非const指针! - Matt
显示剩余7条评论

5
易用性。将str参数标记为const是因为输入参数不会被修改。如果endptr也被标记为const,那么这将告诉调用者他不应该在输出时更改从endptr引用的数据,但通常调用者希望这样做。例如,在获取浮点数后,我可能想要对字符串进行空字符终止:
float StrToFAndTerminate(char *Text) {
    float Num;

    Num = strtof(Text, &Text);
    *Text = '\0';
    return Num;
}

在某些情况下,这是完全合理的要求。如果endptr的类型为const char **,则无法实现此目标。
理想情况下,endptr的常量性应与str的实际输入常量性匹配,但C语言没有通过其语法表示这一点的方法。(Anders Hejlsberg在描述为什么在C#中没有使用const时谈到了这个问题。)

同样的效果可以通过 Text[FloatEnd-Text] = '\0' 轻松实现,所以对我来说这不是一个好的借口。你所指示的方法如果 Text 声明为 const,并且你无法轻松地从执行赋值操作的位置读取它,那么将会产生未定义行为。 - Jens Gustedt
@Jens:我已经更新了代码,以更好地反映基本原理。我同意未定义行为问题。问题是治疗是否比疾病更严重... - Aidan Cully
“could easily be achieved” - 但对于我们来说,过早的优化(比如直接使用指针而不是依赖编译器来处理表达式或者硬件速度足够快以至于我们不需要关心)并不算是过早的优化,但对于 C89 标准委员会来说,他们指定了 strtod。此外,这是一种令人羞愧的习惯用法,所以我们只能想知道它是否比 const 不正确更加令人羞愧 ;-) - Steve Jessop
@Steve:嗯,就算是在算术方面,这也只是“Text+FloatEnd-Text”,这对编译器来说并不难优化,不是吗?-) - Jens Gustedt
@Steve:当然,我有点担心你的反应。1989年的原因可能与今天不同,也许他们没有意识到小关键字“const”的重要性。标准已经修订过了,但是strtof的虚假接口仍然存在。然后,这个接口被转移到了C99的新接口中。这只是一个遗憾,不想多说。 - Jens Gustedt
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接