为什么 printf 说明符格式 %n 不起作用?

Why does printf specifier format %n not work?

这是我的代码:

#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 函数

The conversion specifiers and their meanings are:

(...)

%n The argument shall be a pointer to signed integer into which is written the number of characters written to the output stream so far by this call to fprintf. No argument is converted, but one is consumed. If the conversion specification includes any flags, a field width, or a precision, the behavior is undefined. ...

(...)

这是我在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)

Microsoft documentation 中所述,%n 在您的 MinGW 系统上使用的 Microsoft C 库中默认禁用:

Important

Because the %n format is inherently insecure, it is disabled by default. If %n is encountered in a format string, the invalid parameter handler is invoked, as described in Parameter Validation. To enable %n support, see _set_printf_count_output.

%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