我用C99编写了一个完整的应用程序,并在两个基于GNU/Linux的系统上对其进行了彻底的测试。当尝试在Windows上使用Visual Studio编译它导致应用程序行为不端时,我感到很惊讶。起初我无法断言出了什么问题,但我尝试使用 VC 调试器,然后我发现了有关stdio.h
中声明的fscanf()
函数的差异。
以下代码足以说明该问题:
#include <stdio.h>
int main() {
unsigned num1, num2, num3;
FILE *file = fopen("file.bin", "rb");
fscanf(file, "%u", &num1);
fgetc(file); // consume and discard
fscanf(file, "%u", &num2);
fgetc(file); // ditto
fscanf(file, "%u", &num3);
fgetc(file); // ditto
fclose(file);
printf("%d, %d, %dn", num1, num2, num3);
return 0;
}
假设file.bin正好包含512 256 128
:
$ hexdump -C file.bin
00000000 35 31 32 00 32 35 36 00 31 32 38 00 |512.256.128.|
现在,当在 Ubuntu 机器上根据 GCC 4.8.4 编译时,生成的程序会按预期读取数字并将512, 256, 128
打印到 stdout。
在Windows上使用MinGW 4.8.1编译它会产生相同的预期结果。
但是,当我使用 Visual Studio Community 2015 编译代码时,似乎有一个主要区别;即,输出为:
512, 56, 28
如您所见,尾随的空字符已被fscanf()
使用,因此fgetc()
捕获并丢弃对数据完整性至关重要的字符。
注释掉fgetc()
行会使代码在 VC 中工作,但在 GCC(可能还有其他编译器)中破坏它。
这是怎么回事,我如何将其转换为可移植的 C 代码?我是否遇到了未定义的行为?请注意,我假设的是 C99 标准。
TL;DR:您一直被MSVC不合格问题所困扰,这是一个长期存在的问题,MS从未表现出太大的兴趣来解决。 如果除了符合 C 实现之外还必须支持 MSVC,那么一种方法是使用条件编译指令来抑制通过 MSVC 编译程序时的fgetc()
调用。
我倾向于同意通过格式化的I/O函数读取二进制数据是一个有问题的计划的评论。 然而,更值得怀疑
的是在Windows上使用Visual Studio编译它
和
假设 C99 标准。
据我所知,没有一个版本的 MSVC 符合 C99。 最新版本可能更好地符合 C2011,部分原因是 C2011 使某些功能成为 C99 中必需的可选功能。
但是,无论您使用哪个版本的MSVC,我认为它都不符合该领域的标准(C99和C2011)。 以下是C99第7.19.6.2节的相关文本
转换规范通过以下步骤执行:
[...]
从流 [...] 中读取输入项。输入项定义为最长的输入字符序列,该序列不超过任何指定的字段宽度,并且是匹配输入序列的前缀。输入项后的第一个字符(如果有)保持未读状态。
该标准非常明确,与输入序列不匹配的第一个字符仍然未读取,因此 MSVC 被视为符合要求的唯一方法是,如果