检测scanf格式字符串中常量的不匹配项

myzjeezk  于 2022-12-03  发布在  其他
关注(0)|答案(4)|浏览(136)

scanfman页面:
指示词是下列其中一项:

  • 空白字符序列(空格、制表符、换行符等;这个指令匹配输入中任意数量的白色,包括没有空格。
    *一个普通字符(即白色或' %'之外的字符)。此字符必须与输入的下一个字符完全匹配。(着重号是我的)
  • 一种转换规范,以“%”(百分比)字符开头。根据此规范转换输入的字符序列,并将结果放在相应的指针参数中。如果输入的下一项与转换规范不匹配,则转换失败-这是匹配失败。

下面的代码是:

#include <stdio.h>

int main(void)
{
    const char* fmt = "A %49s B";
    char buf[50];

    printf("%d\n", sscanf("A foo B", fmt, buf));            // 1
    printf("%d\n", sscanf("blah blaaah blah", fmt, buf));   // 0
    printf("%d\n", sscanf("A blah blah", fmt, buf));        // 1

    return 0;
}

第1行和第3行打印1,因为“A”与“A”匹配成功,“foo”/“blah”与%s匹配也成功。第2行打印0,因为“A”不能与“blah”匹配,所以解析在此停止。
这一切都很好,也很符合逻辑,但是在所有转换规范都成功匹配并赋值之后,有没有办法检测到匹配失败?在这种情况下,scanf返回的值将是我的格式字符串中转换说明符的个数,所以我不能用它来判断匹配是否成功,直到最后。
换句话说就是:第3行中输入sscanf的字符串不是“valid”,因为它的格式不是A [something] B。我可以使用scanf来检测它吗?或者strtok是我唯一的选择吗?

3xiyfsfu

3xiyfsfu1#

在格式末尾使用" %n"
指令:
" "扫描0个或多个空白。不会失败。
"%n"保存到目前为止解析的字符数计数(作为int)。它不会失败。
n设置为0并测试它是否发生了更改。只有在前面的整个格式都成功时才会发生更改。还要测试扫描是否在空字符上结束-从而检测到不需要的尾随文本。
添加的" ",虽然是可选的,但如果作为一个典型的尾随空格(通常是'\n')非常有用的话,它并不具有攻击性。它否定了对扫描的文本行进行预处理以删除其行尾的需要。

#include <stdio.h>

void test(const char *s) {
  const char* fmt = "A %49s B %n";
  char buf[50];
  int n = 0;
  int cnt = sscanf(s, fmt, buf, &n);
  int success = n > 0 && s[n] == '\0';
  printf("sscanf():%2d  n:%2d  success:%d  '%s'\n", cnt, n, success, s);
}

int main(void) {
  test("A foo B");
  test("blah blaaah blah");
  test("A blah blah");
  test("A foo B ");
  test("A foo B x");
  test("");
  return 0;
}

输出量

sscanf(): 1  n: 7  success:1  'A foo B'
sscanf(): 0  n: 0  success:0  'blah blaaah blah'
sscanf(): 1  n: 0  success:0  'A blah blah'
sscanf(): 1  n: 8  success:1  'A foo B '
sscanf(): 1  n: 8  success:0  'A foo B x'
sscanf():-1  n: 0  success:0  ''

请注意,成功与否仅由n决定。如果未成功,则不应使用目标扫描变量(如buf)。如果需要部分结果,则使用返回值sscanf()

kfgdxczn

kfgdxczn2#

如果你想解析更复杂的输入,使用一个合适的解析器/lexxer,否则,看看%n转换说明符:
不使用任何输入。相应的参数应该是一个指向有符号整数的指针,该整数将被写入到此fscanf函数调用到目前为止从输入流中读取的字符数。执行%n指令不会增加fscanf函数执行完成时返回的赋值计数。不转换任何参数,但有一个被消耗了。2如果转换说明中包含赋值禁止字符或字段宽度,则该行为未定义。
您可以多次使用此选项:在最后一个变量转换之后和最后一个变量转换之后。

jk9hmnmh

jk9hmnmh3#

对于OP的用例,同样可以使用regex来匹配模式。

/* see http://linux.die.net/man/3/regex */
#include <stdio.h>
#include <stdlib.h>
#include <regex.h>

int regexp(const char *);

int main(int argc, char **argv){
    printf("%d\n", regexp("A foo B"));
    printf("%d\n", regexp("blah blaaah blah"));
    printf("%d\n", regexp("A blah blah"));
    return EXIT_SUCCESS;
}
int regexp(const char *input_str){
    char buf[100];
    regex_t regex;
    int rcval;

    /* compile regexp - see http://linux.die.net/man/3/regcomp */
    rcval = regcomp(&regex, "^A\\s.*\\sB$", 0);
    if (rcval) {
        fprintf(stderr, "Could not compile regex\n");
        return -1;
    }
    /* execute regexp - see http://linux.die.net/man/3/regexec */
    rcval = regexec(&regex, input_str, 0, NULL, 0);
    if (!rcval) {
        fprintf(stdout, "Match\n");
        regfree(&regex);
        return 1;
    }else{
        if (rcval == REG_NOMATCH) {
            fprintf(stdout, "No match\n");
            regfree(&regex);
            return 0;
        }else{
            regerror(rcval, &regex, msgbuf, sizeof(msgbuf));
            fprintf(stderr, "Regex match failed: %s\n", msgbuf);
            regfree(&regex);
            return -1; 
        }
    }
    return 0; // default to no match
}
cgh8pdjw

cgh8pdjw4#

如果您只对是否已扫描整个格式感兴趣,而不关心流中的尾随文本,则可以使用以下简洁的方法:

int success = 0;
fscanf(stream, "A %49s B%n", buf, &success);
if(!success) handleError();

技巧是,除非整个格式都匹配成功,否则"%n"转换不会发生。由于格式的任何匹配都将消耗流中的一个或多个字符,因此"%n"写入的值将始终为非零值。因此,当且仅当整个格式都匹配时,success才会被设置为真实值。

相关问题