【问题标题】:How to check scanf input that is not identifiable by %如何检查 % 无法识别的 scanf 输入
【发布时间】:2019-02-23 00:36:22
【问题描述】:

我有以下代码行从文本文件中读取输入:

a = scanf("(%d)%[^(](%d)(%d)", a1, arr, b1, c1);

来自该文件的正常输入行是: (4)1234(1)(1234)

除了检查%d 之外,scanf() 是否有办法识别与发送的模板格式不匹配的特定输入项? (开头缺少括号等) 例如。 4)1234(5)(1234)

根据我的研究,scanf() 似乎很难做到这一点,甚至可能是不可能的,因为scanf() 返回4(对于这种情况),但我想看看是否有解决方法可以执行。

【问题讨论】:

  • scanf 应该返回成功匹配和分配的输入项的数量,在您的情况下,它是 4,而不是 0,不是 1,并且 EOF 出错。
  • 谢谢@KamilCuk,这很有帮助。有没有办法识别发生错误的特定输入项?例如,如果第一个输入不正确,有没有办法识别它?或者它只是一个可用的支票。 if (a !=4)
  • 单次检查。如果要识别错误,请编写自己的解析器。这需要时间,但这是一个很好的训练。使用 posix 中的getline 读取一行,然后检查所有() 标记,然后在每个整数上调用strtol 并检查错误、超出范围错误等。 scanf 这个名字暗示了它的用途,它是为scan formatted [input] 使用的。它假定输入已经正确格式化,并且在您现在输入有效时主要使用。
  • 您需要为a1b1c1 传递指向整数的指针——您没有显示它们是如何定义的,但它可能不像int a_value; int *a1 = &a_value; 或其他任何东西相似的。 arr 参数至少需要 5 个字符长。如前所述,使用 fgets() 或 POSIX getline() 读取该行,然后使用 sscanf() 解析该行通常是一种很好的处理方式 — 您可以在错误报告中使用完整的输入行,而使用 @ 987654343@ 或fscanf() 直接咀嚼线路,只留下渣渣供你报错使用。
  • 谢谢大家,@KamilCuk 或其他任何人,我正在尝试使用if(a!=4) 检查scanf() 输入是否正确,但它似乎无法正常工作。由于放置数组的代码的%[^(] 部分,我是否应该检查更多值?

标签: c file-io scanf


【解决方案1】:

下面是我的解析器。我使用 gnu 扩展 fmemopen 在内存中打开示例输入,使用 getline 读取整行(和 strerror_r)。
然后我解析整行,错误返回负值,处理strtol可以设置的所有错误,处理超出范围的错误,错误的标记'('和')',太长的行等。

#define _GNU_SOURCE 1
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <errno.h>

/**
 * if expression 'expr' is true, then:
 * print error message including custom message on a single line
 * including errno and strerror
 * then return the value "ret".
 */
#define RET_ON_ERR(expr, ret, msg, ...)  do{ \
    if(expr) { \
        const int  _errno_sav = errno; \
        fprintf(stderr, \
            "%s:%d: %s(): error: expresssion '%s' failed: ", \
            __FILE__, __LINE__, __func__, #expr); \
        if (_errno_sav) { \
            char buf[22]; \
            fprintf(stderr, "errno: %d '%s' ", \
                _errno_sav, strerror_r(_errno_sav, buf, sizeof(buf))); \
            errno = 0; \
        } \
        fprintf(stderr, "" msg "\n", ##__VA_ARGS__); \
        return (ret); \
    } \
}while(0)

/**
 * Same as RET_ON_ERR but the value -__LINE__ is returned
 */
#define RET_LINE_ON_ERR(expr, msg, ...)  RET_ON_ERR((expr), -__LINE__, msg, ##__VA_ARGS__)

/**
 * Returns the array size of a statically allocated array
 */
#define ARRAY_SIZE(arr)  (sizeof(arr)/sizeof(arr[0]))

/**
 * Returns -1 and prints error message on conversion failure
 * @param val0 outputted value
 * @param ptr input string
 * @param endptr0 outputted end pointer
 * @param min minimum value to be returned
 * @param max maximum value to be returned
 * On error negative line number is returned and outputted variables are not set.
 */
int strtol_safe(long *val0, const char ptr[], const char *endptr0[], long min, long max)
{
    assert(val0 != NULL);
    assert(ptr != NULL);

    RET_LINE_ON_ERR(!isdigit(ptr[0]), "string does not start with numbers '%s'", ptr);

    // probably writing my own loop would be faster....
    char *endptr = NULL;
    errno = 0;
    const long val = strtol(ptr, &endptr, 10);
    RET_LINE_ON_ERR(errno == ERANGE && (val == LONG_MAX || val == LONG_MIN), "conversion out of range");
    RET_LINE_ON_ERR(errno != 0, "internal error");
    RET_LINE_ON_ERR(endptr == ptr, "no digits were found");

    RET_LINE_ON_ERR(!(min <= val && val <= max), "%ld out of range (%ld,%ld)", val, min, max);

    *val0 = val;
    if (endptr0 != NULL) {
        *endptr0 = endptr;
    }

    return 0;
}

/**
 * Represents out data!
 */
struct data_s {
    int a1;
    char arr[20];
    int b1;
    int c1;
};

/**
 * Fill single data variable parsing 'line'
 * On error prints errorr message and returns negative value of the line where the error was found.
 */
int data_readline(struct data_s *data, const char line[])
{
    assert(data != NULL);
    assert(line != NULL);
    int tmp;
    long ltmp;

    RET_LINE_ON_ERR(line++[0] != '(', "line '%s' first ( not found", line);

    tmp = strtol_safe(&ltmp, line, &line, INT_MIN, INT_MAX);
    RET_ON_ERR(tmp, tmp, "error on reading first number");
    data->a1 = ltmp;

    RET_LINE_ON_ERR(line++[0] != ')', "line '%s' first ) was not fond", line);

    // read the second field consisting of only numbers until '(' is found
    for (char *out = data->arr; line[0] != '('; ++line, ++out) {
        RET_LINE_ON_ERR(!isdigit(line[0]), 
            "the second token must consist of numbers only");
        RET_LINE_ON_ERR(out - data->arr >= ARRAY_SIZE(data->arr),
            "array overflow when reading the second field from line '%s'", line);
        *out = line[0];
    }

    RET_LINE_ON_ERR(line++[0] != '(', "line '%s' second ( not found", line);

    tmp = strtol_safe(&ltmp, line, &line, INT_MIN, INT_MAX);
    RET_ON_ERR(tmp, tmp, "error on reading first number");
    data->b1 = ltmp;

    RET_LINE_ON_ERR(line++[0] != ')', "line '%s' second ) not found", line);

    RET_LINE_ON_ERR(line++[0] != '(', "line '%s' third ( not found", line);

    tmp = strtol_safe(&ltmp, line, &line, INT_MIN, INT_MAX);
    RET_ON_ERR(tmp, tmp, "error on reading first number");
    data->c1 = ltmp;

    RET_LINE_ON_ERR(line++[0] != ')', "line '%s' third ) not found", line);

    RET_LINE_ON_ERR(line[0] != '\n' && line[0] != '\0', 
        "line '%s' does not end after reading tokens", line);

    return 0;
}


int main()
{
    // create FILE object reading from custom buffer, just for testing
    char buf[] = "(4)1234(1)(1234)";
    FILE * const f = fmemopen(buf, ARRAY_SIZE(buf), "r");
    RET_LINE_ON_ERR(f == NULL, "error fmemopen");

    // read one line of input.
    char *line = NULL;
    size_t n = 0;
    RET_LINE_ON_ERR(getline(&line, &n, f) < 0, "getline error");

    // read data structure
    struct data_s data = {0};
    int ret;
    ret = data_readline(&data, line);
    if (ret < 0) {
        fprintf(stderr, "error from data_readline: %d\n", ret);
        exit(-1);
    }

    // print output
    printf("readed first line consist of:\n"
            "a1=%d arr='%s' b1=%d c1=%d\n"
            , 
            data.a1, data.arr, data.b1, data.c1
    );

    close(f);
    return 0;
}

onlinegdb 上提供实时版本。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-12-25
    • 1970-01-01
    • 1970-01-01
    • 2020-05-24
    • 1970-01-01
    • 2020-06-04
    • 1970-01-01
    • 2022-06-30
    相关资源
    最近更新 更多