10

在我的实验中,这个表达式

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

d用输入字符串中的字符初始化3.0并放置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'in 结尾。这对我来说看起来很奇怪,原因有两个。

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

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

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

所以,我的问题是,在 C 语言标准文档中,它究竟允许在哪里sscanf表现如上所述,参考以上两点:消耗更多strtod并成功地将这样的序列转换为"3e"

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

4

1 回答 1

1

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

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

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

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

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

无穷大是“INF”或“INFINITY”,不考虑大小写。

NAN 是“NAN”(不考虑大小写)可选地后跟“(”,一个字符序列,后跟“)”。字符串以与实现相关的方式指定 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 会尝试捕获更多字符,并且如果它判断它当前是合法的,则不会回滚(可能在不完整的情况下是非法的)。

于 2014-10-15T09:09:21.073 回答