scanf正在覆盖第二个变量

4
我要求用户输入两个变量,首先是无符号整数a,其次是无符号字符b。读取a时工作正常,但在读取b之后,a的值为0。
我发现指向a的指针比指向b的指针多1。当b大于255时,我意识到a不再为0。所以我认为scanf读取了b超过一个字节,并覆盖了a的值。
#include <stdio.h>

int main ()
{
    unsigned int a;
    unsigned char b;

    printf("a: ");
    scanf("%u", &a); /* 255 */
    printf("b: ");
    scanf("%hhu", &b); /* 17 */

    printf("a: %u\n", a); /* a: 0 */
    printf("b: %u\n", b); /* b: 17 */

    printf("pointer a: %u\n", &a); /* pointer a: 6422316 */
    printf("pointer b: %u\n", &b); /* pointer b: 6422315 */

    return 0;
}

因为我对 C 编程还不熟悉,不确定需要哪些信息才能理解我的问题。我正在使用一款 64 位处理器和以下编译器:

gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=c:/mingw/bin/../libexec/gcc/mingw32/8.2.0/lto-wrapper.exe
Target: mingw32
Configured with: ../src/gcc-8.2.0/configure --build=x86_64-pc-linux-gnu --host=mingw32 --target=mingw32 --prefix=/mingw --disable-win32-registry --with-arch=i586 --with-tune=generic --enable-languages=c,c++,objc,obj-c++,fortran,ada --with-pkgversion='MinGW.org GCC-8.2.0-3' --with-gmp=/mingw --with-mpfr=/mingw --with-mpc=/mingw --enable-static --enable-shared --enable-threads --with-dwarf2 --disable-sjlj-exceptions --enable-version-specific-runtime-libs --with-libiconv-prefix=/mingw --with-libintl-prefix=/mingw --enable-libstdcxx-debug --with-isl=/mingw --enable-libgomp --disable-libvtv --enable-nls --disable-build-format-warnings
Thread model: win32
gcc version 8.2.0 (MinGW.org GCC-8.2.0-3)

3
Nitpick: printf("pointer a: %u\n", &a); 应该改为 printf("pointer a: %p\n", (void*)&a);b 的语句也应该这样修改。 - Sourav Ghosh
1
无法重现 https://ideone.com/dsw2I6,请提供完整的 I/O 会话。 - Eugene Sh.
2
代码是正确的,除了用于打印格式的%u之外,行为类似于预期的未定义行为,因为布局和格式%u用于读取无符号字符... - Antti Haapala -- Слава Україні
1
@JL2210 hh 不是GNU扩展 http://port70.net/~nsz/c/c11/n1570.html#7.21.6.2p11 - Eugene Sh.
1
MSVC6.0是出了名的糟糕。例如,它完全搞错了for循环作用域。 - Antti Haapala -- Слава Україні
显示剩余12条评论
2个回答

5
问题在于这一行代码:
    scanf("%hhu", &b); /* 17 */

微软的C运行时库没有实现%hhu(在C99之前不是C标准的一部分)。这导致输入被解释为short(由于MSVCRT将hh解释为格式说明符中的h),并写入超过b的限制并覆盖了a的部分内容。

要解决此问题,请将a的类型更改为unsigned short,使用%hu作为格式说明符或向编译器传递-D__USE_MINGW_ANSI_STDIO标志。


1
更准确地说,MinGW使用MSVCRT.DLL,这是唯一实际随Windows捆绑的C运行时库(这就是为什么他们使用它),但是Microsoft有意多年来没有更新它,而且真的不希望新应用程序使用它。我曾经以为MinGW64正在努力改变这个情况,但我可能是错误的和/或过时了。 - zwol
2
微软公司应该为直到十多年后才在其编译器中实现C99而受到很大的责备,但MinGW在特定情况下使用MSVCRT.DLL也有一些责任。 - zwol
@JL2210 感谢您的解释。这个标志 -D__USE_MINGW_ANSI_STDIO 是做什么用的? - Kaniee
@Kaniee 它使MinGW的ANSI兼容stdio实现得以启用。 - S.S. Anne

4
这是关于在使用MinGW编译的代码中打印size_t值时使用%zu的各种问题的近似副本。在C99中,zhh长度修饰符均出现过。
C89将长度修饰符描述如下:
可选的hl(小写的L)或L,表示接收对象的大小。如果相应的参数是指向short int而不是指向int的指针,则转换说明符din应该在前面加上h,或者如果它是指向long int的指针,则应该在前面加上l。类似地,如果相应的参数是指向unsigned short int而不是指向unsigned int的指针,则转换说明符oux应该在前面加上h,或者如果它是指向unsigned long int的指针,则应该在前面加上l。最后,如果相应的参数是指向double而不是指向float的指针,则转换说明符efg应该在前面加上K,或者如果它是指向long double的指针,则应该在前面加上L。如果hlL与任何其他转换说明符一起出现,则行为是未定义的。
“hh”长度修饰符出现在C99中。C11 7.21.6.1p7中指出:
“hh”指定下一个“d”、“i”、“o”、“u”、“x”或“X”转换说明符适用于有符号字符或无符号字符参数(根据整数提升进行了升级,但其值在打印之前将被转换为有符号字符或无符号字符);或者下一个“n”转换说明符适用于指向有符号字符参数的指针。

MinGW使用MSVCRT.DLL作为C库——该库仅支持C89。在看到%hhu时,行为根据C89是未定义的,但根据解释为%hu并在从&a开始的2个字节写入一个unsigned short值是一致的。


解决方案是将值读入临时的unsigned shortunsigned int,然后将其分配给unsigned char

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接