为什么printf的格式说明符%n不能工作?

5
这是我的代码:
#include <stdio.h>

int main(void) {
    int n;

    fprintf(stdout, "Hello%n World\n", &n);
    fprintf(stdout, "n: %d\n", n);

    return 0;
} 

这是我的输出:
Hellon: 0
  1. 为什么fprintf格式说明符"%n"不能使用?
  2. 为什么要打印的字符串被中断?

ISO/IEC 9899:201x C11 - 7.21.6.1 - fprintf函数

转换说明符及其含义如下:

(...)

%n 参数必须是指向带符号整数的指针,它将写入到调用此函数时已写入输出流的字符数。 没有参数被转换,但是有一个参数被使用。如果转换说明符包括任何标志、字段宽度或精度,则行为是未定义的。...

(...)

这是我在Code::Blocks上使用的编译器版本:

Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=C:/Program\ Files/mingw-w64/x86_64-8.1.0-posix-seh-rt_v6-rev
0/mingw64/bin/../libexec/gcc/x86_64-w64-mingw32/8.1.0/lto-wrapper.exe
Target: x86_64-w64-mingw32
Configured with: ../../../src/gcc-8.1.0/configure --host=x86_64-w64-mingw32 --bu
ild=x86_64-w64-mingw32 --target=x86_64-w64-mingw32 --prefix=/mingw64 --with-sysr
oot=/c/mingw810/x86_64-810-posix-seh-rt_v6-rev0/mingw64 --enable-shared --enable
-static --disable-multilib --enable-languages=c,c++,fortran,lto --enable-libstdc
xx-time=yes --enable-threads=posix --enable-libgomp --enable-libatomic --enable-
lto --enable-graphite --enable-checking=release --enable-fully-dynamic-string --
enable-version-specific-runtime-libs --disable-libstdcxx-pch --disable-libstdcxx
-debug --enable-bootstrap --disable-rpath --disable-win32-registry --disable-nls
 --disable-werror --disable-symvers --with-gnu-as --with-gnu-ld --with-arch=noco
na --with-tune=core2 --with-libiconv --with-system-zlib --with-gmp=/c/mingw810/p
rerequisites/x86_64-w64-mingw32-static --with-mpfr=/c/mingw810/prerequisites/x86
_64-w64-mingw32-static --with-mpc=/c/mingw810/prerequisites/x86_64-w64-mingw32-s
tatic --with-isl=/c/mingw810/prerequisites/x86_64-w64-mingw32-static --with-pkgv
ersion='x86_64-posix-seh-rev0, Built by MinGW-W64 project' --with-bugurl=https:/
/sourceforge.net/projects/mingw-w64 CFLAGS='-O2 -pipe -fno-ident -I/c/mingw810/x
86_64-810-posix-seh-rt_v6-rev0/mingw64/opt/include -I/c/mingw810/prerequisites/x
86_64-zlib-static/include -I/c/mingw810/prerequisites/x86_64-w64-mingw32-static/
include' CXXFLAGS='-O2 -pipe -fno-ident -I/c/mingw810/x86_64-810-posix-seh-rt_v6
-rev0/mingw64/opt/include -I/c/mingw810/prerequisites/x86_64-zlib-static/include
 -I/c/mingw810/prerequisites/x86_64-w64-mingw32-static/include' CPPFLAGS=' -I/c/
mingw810/x86_64-810-posix-seh-rt_v6-rev0/mingw64/opt/include -I/c/mingw810/prere
quisites/x86_64-zlib-static/include -I/c/mingw810/prerequisites/x86_64-w64-mingw
32-static/include' LDFLAGS='-pipe -fno-ident -L/c/mingw810/x86_64-810-posix-seh-
rt_v6-rev0/mingw64/opt/lib -L/c/mingw810/prerequisites/x86_64-zlib-static/lib -L
/c/mingw810/prerequisites/x86_64-w64-mingw32-static/lib '
Thread model: posix
gcc version 8.1.0 (x86_64-posix-seh-rev0, Built by MinGW-W64 project)

1
你试图打印一个未初始化的整数。由于它未被初始化,行为是未定义的。编译器可以自由地从你的代码中消除这个整数,然后“损坏”printf调用。 - Paul Ogilvie
类型 C、n、p 和 S,以及 c 和 s 在 printf 函数中的行为是 Microsoft 的扩展,不符合 ANSI 标准。 - Paul Ogilvie
1
我有点困惑。即使是 ANSI C 也支持 %n,所以这不是缺少“-std=c99”选项的情况(gcc 曾经默认为“-std=gnu89”)。而且快速检查(https://ideone.com/QWyysF)显示它应该正常工作。你问题中的代码和你文件中的代码一样吗?我的意思是,你确定“Hello%n World\n”字符串中没有奇怪的非 ASCII 字符吗?即使看起来相同,错误的 % 或 n 字符也可能是一个问题。 - Alexey Frunze
2
OP正在使用mingw,它使用(一个古老版本的)MSVC标准库实现,所以这很可能只是MSVC故意不遵从标准。 - R.. GitHub STOP HELPING ICE
4
微软因持有一个错误的信念,认为 %n 是不安全的,并禁用它作为一种加固措施。 - R.. GitHub STOP HELPING ICE
显示剩余3条评论
1个回答

11

根据Microsoft文档所述,MinGW系统使用的Microsoft C库默认禁用了%n

重要提示

由于%n格式本身存在不安全性,因此默认情况下已禁用。如果在格式字符串中遇到%n,将调用无效参数处理程序,如参数验证中所述。要启用%n支持,请参见_set_printf_count_output

然而,是否像Microsoft所声称的那样,%n实际上存在安全隐患还有待商榷。为了支持这一声明,展示了结合使用变量格式字符串和缓冲区溢出错误被攻击者修改的printf函数的示例。

在某些Microsoft系统中(但可能不包括最新版本),您可以通过以下方式修复程序:

#include <stdio.h>

int main(void) {
    int n;

    _set_printf_count_output(1);

    fprintf(stdout, "Hello%n World\n", &n);
    fprintf(stdout, "n: %d\n", n);

    return 0;
} 

如果需要更加便携的方法,以下解决方案可以避免使用 %n 并且仍然能够获得相同的结果:

#include <stdio.h>

int main(void) {
    int n;

    n = fprintf(stdout, "Hello");
    fprintf(stdout, " World\n");
    fprintf(stdout, "n: %d\n", n);

    return 0;
} 

输出:

Hello World
n: 5

2
一个可变格式字符串,可以通过缓冲区溢出错误被攻击者更改。微软在这里进行了美妙的责备。而且它破坏了可移植性。多么方便啊。 - Andrew Henle
这在Visual Studio上可以工作,但如果我在MinGW上使用_set_printf_count_output(1);则会出现以下错误:“undefined reference to '__imp_set_printf_count_output'”。 - Alessandro Avolio
@AlessandroAvolio:好的,无论如何这都是一个不可移植的解决方案。我会用更好的方法修改答案。 - chqrlie
2
MinGW有没有一种方法可以在不更改源代码的情况下通过链接正确的库文件来解决这个问题?我认为他们甚至提供了一个增强版的libc,修复了所有printf的错误。 - R.. GitHub STOP HELPING ICE
当然,您可以链接自己的.o文件,并使用调用此答案中函数的ctor,但我有点记得他们提供了一种开箱即用的方法来实现这一点... - R.. GitHub STOP HELPING ICE
@AlessandroAvolio:我怀疑问题的原因是mingw正在使用新的MSVCRT dll,默认情况下缺少%n,但一些古老的导入库是在_set_printf_count_output存在之前创建的。 - R.. GitHub STOP HELPING ICE

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