`strtod("3ex", &end)` 的结果应该是啥? `sscanf` 呢?

Posted

技术标签:

【中文标题】`strtod("3ex", &end)` 的结果应该是啥? `sscanf` 呢?【英文标题】:What is the result of `strtod("3ex", &end)` supposed to be? What about `sscanf`?`strtod("3ex", &end)` 的结果应该是什么? `sscanf` 呢? 【发布时间】:2014-12-07 16:30:43 【问题描述】:

在我的实验中这个表达式

double d = strtod("3ex", &end);

3.0 初始化d,并将end 指针放在输入字符串中的'e' 字符处。这正是我期望它的行为。 'e' 字符可能看起来是指数部分的开头,但由于缺少实际的指数值(6.4.4.2 要求),因此 'e' 应被视为完全独立的字符。

但是,当我这样做时

double d;
char c;
sscanf("3ex", "%lf%c", &d, &c);

我注意到sscanf 使用'3''e'%lf 格式说明符。变量d 接收3.0 值。变量c'x' 结束。这对我来说看起来很奇怪,原因有两个。

首先,由于语言规范在描述%f 格式说明符的行为时引用了strtod,我直观地期望%lf 以与strtod 相同的方式处理输入(即选择与终止点)。但是,我知道从历史上看,scanf 应该不超过一个字符返回输入流。这限制了任何前瞻scanf 可以由一个字符执行的距离。上面的例子需要至少两个字符的前瞻。所以,假设我接受 %lf 消耗了输入流中的 '3''e' 的事实。

但是我们遇到了第二个问题。现在sscanf 必须将"3e" 转换为类型double"3e" 不是浮点常量的有效表示(同样,根据 6.4.4.2,指数值不是可选的)。我希望sscanf 将此输入视为错误:在%lf 转换期间终止,返回0 并保持dc 不变。但是,上述sscanf 成功完成(返回2)。

这种行为在标准库的 GCC 和 MSVC 实现之间是一致的。

所以,我的问题是,在 C 语言标准文档中,它究竟允许sscanf 的行为如上所述,参考以上两点:消耗超过strtod 并成功转换为@ 等序列987654360@?

通过查看我的实验结果,我可能可以“逆向工程”sscanf 的行为:消耗尽可能多的“看起来正确”,从不后退,然后将消耗的序列传递给 strtod。这样'e'%lf 消耗,然后被strtod 忽略。但是语言规范中的所有内容都是这样吗?

【问题讨论】:

@HighPredator: OP 可能意味着变量c 应该达到值'e' 而不是值'x'。或者它根本不应该获得任何值,并且函数 sscanf 应该返回 1 而不是 2(因此它准确地模拟了 strtod 的行为)。 @HighPredator:我实际上描述了我在问题中遇到的两个问题。我一直直观地期望sscanf 格式要求和行为与strto... 格式要求和行为同步。语言标准实际上说明了这一点,但显然我在其中看到了更多的东西。例如,我预计 sscanf 会在 strto... 停止的同一点停止。现在我有点“看到”标准可能不需要这样做,并允许sscanf 消耗更多。 虽然您观察到的行为看起来有点奇怪,但 sscanfstrtod 并不要求表现出相似(或等效)的行为。 strto. *scanf() 需要从左到右扫描。但是strtod() 可能会“向前看”并决定将 endptr 放在哪里。 @Blue Moon:是的,但是语言规范通过简单地引用strtod来定义f格式说明符的行为。如果f 说明符和strtod 之间存在差异,则标准应在某处对其进行描述。我的问题是:在哪里?具体是哪个措辞? 一个有趣的重复案例——与其说是 question,不如说是 answer:Difference between scanf() and strtol() / strtod() in parsing numbers 基本上,...scanf() 被定义为取最长的可能序列,即或者是匹配输入的前缀,而strto...()取最长的有效序列。 (区别在于流只支持一个保证回退的字符,即...scanf() 不能像strto...() 那样回退。) 【参考方案1】:

我只是在die.net找到下面的描述

strtod()、strtof() 和 strtold() 函数将初始 nptr 指向的字符串的一部分为 double、float 和 long 分别表示双重表示。

字符串(初始部分)的预期形式是可选的 isspace(3) 识别的前导空白,可选加号 ('+') 或减号 ('-'),然后是 (i) 十进制数,或 (ii) 十六进制数,或 (iii) 无穷大,或 (iv) NAN (不是数字)。

十进制数由十进制数字的非空序列组成 可能包含一个基数字符(小数点, 取决于语言环境,通常是 '.'),可选地后跟一个小数 指数。十进制指数由一个“E”或“e”组成,后跟一个 可选的加号或减号,后跟一个非空序列 十进制数字,表示乘以 10 的幂。

十六进制数由“0x”或“0X”后跟非空字符组成 可能包含基数字符的十六进制数字序列, 可选地后跟一个二进制指数。二进制指数包括 'P' 或 'p',后跟可选的加号或减号,后跟 由十进制数字的非空序列,并表示乘法 2 的幂。基数字符和二进制指数中的至少一个 必须在场。

无论大小写,无穷大要么是“INF”,要么是“INFINITY”。

NAN 是“NAN”(不考虑大小写)可选地后跟 '(', a 字符序列,后跟')'。字符串 以与实现相关的方式指定 NAN 的类型。

然后我做了一个实验,我用gcc执行了下面的代码

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

char head[1024], *tail;

void core(const char *stmt)
    sprintf(head, "%s", stmt);
    double d=strtod(head, &tail);
    printf("cover %s to %.2f with length=%ld.\n", head, d, tail-head);


int main()
    core("3.0x");
    core("3e");
    core("3ex");
    core("3e0x");

    return 0;

并得到结果

cover 3.0x to 3.00 with length=3.
cover 3e to 3.00 with length=1.
cover 3ex to 3.00 with length=1.
cover 3e0x to 3.00 with length=3.

所以,'e'后面似乎应该有一些数字。

对于sscanf,我用gcc代码做了另一个实验:

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

char head[1024];

void core(const char *stmt)
    int i;sscanf(stmt, "%x%s", &i, head);
    printf("sscanf %s catch %d with '%s'.\n", stmt, i, head);


int main()
    core("0");
    core("0x0g");
    core("0x1g");
    core("0xg");

    return 0;

然后得到下面的输出:

sscanf 0 catch 0 with ''.
sscanf 0x0g catch 0 with 'g'.
sscanf 0x1g catch 1 with 'g'.
sscanf 0xg catch 0 with 'g'.

似乎 sscanf 会尝试捕获更多字符,并且如果它判断它当前是合法的,则不会回滚(可能在不完整的情况下是非法的)。

【讨论】:

以上是关于`strtod("3ex", &end)` 的结果应该是啥? `sscanf` 呢?的主要内容,如果未能解决你的问题,请参考以下文章

c++中如何将字符串类似于s="1 2 3 4 5 6 7 8 9"转换成整型数组a=1,2,3,4,5,6,7,8,9,

C语言,字符串转成16进制或者二进制写入二进制文件

c语言strtod()函数的用法

strtol、strtod 不安全吗?

LynxOS strtod 与 Linux 不一样

strtok() and strtod()