fscanf()在不同编译器中的行为不一致(消耗尾随空字符)

rhi*_*ino 5 c scanf stdio null-character

我在C99中编写了一个完整的应用程序,并在两个基于GNU/Linux的系统上进行了彻底的测试.当尝试使用Windows上的Visual Studio编译它导致应用程序行为不端时,我感到很惊讶.起初我无法断言出了什么问题,但我尝试使用VC调试器,然后我发现了与fscanf()声明的函数有关的差异stdio.h.

以下代码足以证明问题:

#include <stdio.h>

int main() {
    unsigned num1, num2, num3;

    FILE *file = fopen("file.bin", "rb");
    fscanf(file, "%u", &num1);
    fgetc(file); // consume and discard \0
    fscanf(file, "%u", &num2);
    fgetc(file); // ditto
    fscanf(file, "%u", &num3);
    fgetc(file); // ditto
    fclose(file);

    printf("%d, %d, %d\n", num1, num2, num3);

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

假设file.bin包含512\0256\0128\0:

$ hexdump -C file.bin
00000000  35 31 32 00 32 35 36 00  31 32 38 00              |512.256.128.|
Run Code Online (Sandbox Code Playgroud)

现在,当在Ubuntu机器上的GCC 4.8.4下编译时,生成的程序按预期读取数字并打印512, 256, 128到stdout.
在Windows上使用MinGW 4.8.1进行编译会得到相同的预期结果.

但是,当我使用Visual Studio Community 2015编译代码时似乎存在重大差异; 即输出是:

512, 56, 28
Run Code Online (Sandbox Code Playgroud)

如您所见,尾随空字符已被消耗fscanf(),因此fgetc()捕获并丢弃对数据完整性至关重要的字符.

注释掉这些fgetc()代码会使代码在VC中运行,但在GCC(以及可能的其他编译器)中会破坏它.

这里发生了什么,如何将其转换为可移植的C代码?我是否遇到了未定义的行为?请注意,我假设是C99标准.

Joh*_*ger 8

TL; DR:你被MSVC不合格所困扰,这是MS长期以来从未对解决问题表现出太大兴趣的问题.如果除了符合C实现之外必须支持MSVC,那么一种方法是使用条件编译指令来抑制fgetc()通过MSVC编译程序时的调用.


我倾向于同意通过格式化I/O函数读取二进制数据是一个值得怀疑的计划.然而,更值得怀疑的是它的组合

在Windows上使用Visual Studio编译它

假设C99标准.

据我所知,没有任何版本的MSVC符合C99.最近的版本可能会更好地符合C2011,部分原因是因为C2011使得某些功能在C99中是必需的.

但是,无论您使用哪种版本的MSVC,我认为它都不符合该领域的标准(包括C99和C2011).以下是C99第7.19.6.2节的相关文字

转换规范按以下步骤执行:

[...]

从流中读取输入项[...].输入项被定义为输入字符的最长序列,其不超过任何指定的字段宽度,并且是匹配的输入序列的前缀,或者是匹配的输入序列的前缀.输入项目之后的第一个字符(如果有)仍未读取.

标准非常清楚,与输入序列不匹配的第一个字符仍然未读,因此MSVC被认为符合的唯一方式是\0字符是否可以被解释为匹配输入序列的一部分(并终止),或者如果fgetc()被允许跳过\0字符.我认为后者没有任何理由,特别是考虑到流以二进制模式打开,所以让我们考虑前者.

对于u转换说明符,匹配的输入序列定义

匹配一个可选的带符号十进制整数,其格式与strtoul函数的主题序列的预期相同,其基值参数值为10.

"strtoul函数的主题序列" 在该函数的规范中定义:

首先,它们将输入字符串分解为三个部分:初始的,可能为空的白色空格字符序列(由isspace函数指定),类似于由base的值确定的某个基数表示的整数的主题序列,以及一个或多个无法识别的字符的最终字符串,包括输入字符串的终止空字符.

请特别注意,终止空字符显式归因于无法识别字符的最终字符串.它不是主题字符串的一部分,因此fscanf()在根据u说明符转换输入时不应该匹配.