【问题标题】:Is it better to use one scanf() (or fscanf()) or it doesn't matter?使用一个scanf()(或fscanf())更好还是没关系?
【发布时间】:2021-10-25 22:35:49
【问题描述】:

我听说scanf()“花费很多”,但现在我找不到任何相关信息。因此,我是使用一个fscanf("%f", &num) 还是在一行中查找多个值(fscanf("%f %f %f %f", &num, &num1, &num2, &num3))是否重要?附加问题:您能否推荐一些资源来获取此类信息,了解一项功能对程序的成本是多少?

我知道标题中有scanf(),但是fscanf()、sscanf()和scanf()可以在手册的同一页找到,所以我相信标题没有误导。

【问题讨论】:

  • 最好使用零scanf
  • @WilliamPursell,你为什么这么说?我认为scanf() 很有用,除非你知道如何处理它。
  • 用 scanf 可靠地处理输入是极其困难的。即使使用简单的scanf("%f"),对于超出浮点数范围的输入,行为也将未定义。为防止这种情况,您必须添加最大字段宽度,但如果这样做,您会极大地限制可以输入的值的数量。它在玩具程序中很有用,但除了简单的练习之外,它不适合任何东西。
  • 在讨论为什么不使用scanf 时,本指南可能会有所帮助:A beginners' guide away from scanf()

标签: c input scanf


【解决方案1】:

它们的性能应该完全相同。

fscanf 从打开的文件流中读取; “File Scan Formated”。

scanf 做同样的事情,但它只从stdin 读取。 scanf(format, ...) 就是 fscanf(stdin, format, ...)

附加问题:您能否推荐一些资源来获取此类信息,了解某个功能对程序的成本是多少?

这将取决于您的实施。事实上,像scanf 这样的函数不太可能是性能问题。它们已经优化了几十年。性能问题来自循环和糟糕的算法。查看 Big-O Notation 等主题了解基础知识,查看 benchmarking and profilers 等工具来测试正在运行的程序。

您应该关注的是the many problems with scanf and fscanf。最大的问题是他们将读取输入与解析联系在一起。考虑使用fgetssscanf(S 表示字符串)来分隔两者:读取一行,解析一行。

【讨论】:

  • 为什么说读取和解析输入有问题?如果我们知道我们应该从输出中得到什么,那么 fscanf() 应该不是问题——如果我们还知道如何处理这个函数
  • @Funny 因为他们有不同的错误处理。使用 fscanf 更难知道是否存在读取问题和解析问题。而你永远不知道会发生什么。如果您对输入做出假设,您的程序很容易受到各种攻击和错误的影响。读取和解析是两个复杂的问题;把它们分开。
【解决方案2】:

我听说 scanf() “花费很多”,但现在我找不到任何相关信息。

是的,这是真的。在许多情况下它会比较慢。

特别是逐字节读取文件:

int chr;

// this is much slower than ...
while (1) {
    if (fscanf(xf,"%c",&chr) != 1)
        break;
}

// ... this
while (1) {
    chr = fgetc(xf);
    if (chr == EOF)
        break;
}

旁注:信不信由你,以上来自我遇到的一个真实世界的生产级程序[用于将固件加载到 FPGA 中]。我使用fgetc [和read] 修复了它。我将运行时间从 15 分钟减少到 90 秒


附加问题:您能否推荐一些资源来获取此类信息,了解某个功能对程序的成本是多少?

您可以了解“大 O”符号和分析。

但是,您始终可以编写基准程序。这是我创建的一个,可以帮助您创建自己的。

在这种特殊情况下,重要的是从时序中消除 I/O 的开销。因此,该程序在 缓冲区 中创建随机行,并在缓冲区上执行 strtodsscanf

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

#define TSTMAX      100000
#define FNCMAX      2

typedef long long tsc_t;

tsc_t
tscget(void)
{
    struct timespec ts;
    tsc_t tsc;

    clock_gettime(CLOCK_MONOTONIC,&ts);

    tsc = ts.tv_sec;
    tsc *= 1000000000;
    tsc += ts.tv_nsec;

    return tsc;
}

double
tscsec(tsc_t tsc)
{
    double sec;

    sec = tsc;
    sec /= 1e9;

    return sec;
}

tsc_t elap[FNCMAX][TSTMAX];

void
useit(double *arr)
{
}

void
doscanf1(char *buf)
{
    double arr[4];

    sscanf(buf,"%lf %lf %lf %lf",&arr[0],&arr[1],&arr[2],&arr[3]);

    useit(arr);
}

void
doscanf2(char *buf)
{
    double arr[4];

    sscanf(buf,"%lf %lf %lf %lf",&arr[0],&arr[1],&arr[2],&arr[3]);

    useit(arr);
}

void
dotok(char *buf)
{
    double arr[4];

    for (int idx = 0;  idx < 4;  ++idx)
        arr[idx] = strtod(buf,&buf);

    useit(arr);
}

void
dofnc(int tstidx,int fncidx,const char *src)
{
    char buf[1000];
    tsc_t tscbeg;
    tsc_t tscend;

    strcpy(buf,src);

    tscbeg = tscget();

    switch (fncidx) {
    case 0:
        dotok(buf);
        break;
    case 1:
        doscanf1(buf);
        break;
    case 2:
        doscanf2(buf);
        break;
    }

    tscend = tscget();
    tscend -= tscbeg;

    elap[fncidx][tstidx] = tscend;
}

void
dotest(int tstidx)
{
    char *bp;
    char buf[1000];

    bp = buf;
    for (int idx = 0;  idx < 4;  ++idx)
        bp += sprintf(bp," %.8g",drand48());

    dofnc(tstidx,0,buf);
    dofnc(tstidx,1,buf);
}

int
cmpfnc(const void *lhs,const void *rhs)
{
    tsc_t dif = *(const tsc_t *) lhs - *(const tsc_t *) rhs;
    int cmpflg;

    do {
        cmpflg = -1;
        if (dif < 0)
            break;

        cmpflg = 1;
        if (dif > 0)
            break;

        cmpflg = 0;
    } while (0);

    return cmpflg;
}

int
main(void)
{
    tsc_t avg[FNCMAX] = { 0 };

    for (int tstidx = 0;  tstidx < TSTMAX;  ++tstidx)
        dotest(tstidx);

    for (int fncidx = 0;  fncidx < FNCMAX;  ++fncidx)
        qsort(&elap[fncidx][0],TSTMAX,sizeof(tsc_t),cmpfnc);

    for (int tstidx = 0;  tstidx < TSTMAX;  ++tstidx) {
        for (int fncidx = 0;  fncidx < FNCMAX;  ++fncidx) {
            tsc_t tsc = elap[fncidx][tstidx];
            printf(" %.9f",tscsec(tsc));
            avg[fncidx] += tsc;
        }
        printf(" %d\n",tstidx);
    }

    for (int fncidx = 0;  fncidx < FNCMAX;  ++fncidx) {
        tsc_t tsc = avg[fncidx];
        printf("TOT:%.9f AVG:%.9f\n",tscsec(tsc),tscsec(tsc) / TSTMAX);
    }
}

您可以运行上述程序。以下是在我的系统上运行的摘要:

TOT:0.090690979 AVG:0.000000907
TOT:0.157146016 AVG:0.000001571

因此,使用 strtod 几乎比 sscanf 快 1.75 倍

【讨论】:

  • 但是,fscanf() 不是比strtod() 更容易检查错误吗?无论如何,我总能在我的文件中遇到真正的 0.0 值。另外,useit() 有什么作用?
  • useit 是为了抑制关于“arr set but not used”的多余编译器警告
  • &amp;buf 作为第二个参数传递给strtod 有什么意义?如果覆盖buf,则无法将其与buf 的原始值进行比较,因此无法检查转换错误,那么传入第二个参数有什么意义呢?如果您不想检查转换错误,为什么不直接传入 NULL 作为第二个参数?
  • @AndreasWenzel &amp;buf 用于推进指针。否则,后续调用将重新解析 first 令牌(例如,对于 1 2 3 4 的缓冲区,所有 arr[idx] 将是 1)。对于1@ 2 的缓冲区,与原始buf 指针值进行比较将不会检测到转换错误。您可以在调用后通过查看*buf 来检查转换错误(例如,它必须是`, \n`,或0 等)
  • @Funny 检查strtod 中的错误不是通过检查返回值,而是通过查看开始和结束指针是否相同。 Example。迟钝,但一旦你知道你就知道了。那是你的C;这并不容易,只是可能。 fscanf 的返回值混淆了输入和解析错误,因此很难判断发生了什么以及如何恢复。
猜你喜欢
  • 1970-01-01
  • 2020-01-01
  • 2017-07-04
  • 2014-10-05
  • 2021-06-09
  • 2010-10-12
  • 2011-03-05
  • 2014-01-25
相关资源
最近更新 更多