Cでの%n形式指定子の使用は何ですか?


125

%nC でのフォーマット指定子の使用は何ですか?誰かが例で説明できますか?


25
ファイン・マニュアルを読むというファインアートはどうなっていますか?
イェンス

8
本当の質問は、このようなオプションのポイントは何ですか?印刷されるcharの数の値を知りたいのはなぜでしょうか?それは、開発者が退屈してカーネルにバグを導入することにしたようなものでした
chen

それが、バイオニックが手放した理由です。
solidak 2018

1
これは実際には有効な質問であり、細かいマニュアルではおそらく答えられないでしょう。誤ってTuringを完全にして%nしまうことが発見されました。printfたとえば、それにBrainfuckを実装できます。github.com / HexHive
ジョン・フレーザー

回答:


147

何も印刷されません。引数は、それまでに書き込まれた文字数が格納される、signed intへのポインターでなければなりません。

#include <stdio.h>

int main()
{
  int val;

  printf("blah %n blah\n", &val);

  printf("val = %d\n", val);

  return 0;

}

前のコードは次のように出力します。

blah  blah
val = 5

1
あなたは引数がsigned intへのポインタでなければならないことに言及し、そしてあなたはあなたの例でおそらくsigned intを使用しました(おそらく単なるタイプミス)。
bta

1
@AndrewS:関数が変数の値を変更するため。
Jack

3
@Jack intは常に署名されています。
jamesdlin 14年

1
@jamesdlin:私の間違い。すみません。どこで読んだかわかりませんでした。
Jack

1
なんらかの理由で、サンプルはnoteでエラーを発生させますn format specifies disabled。どういう理由ですか?
Johnny_D 2014

186

これらの回答のほとんどは何をするか(何も出力%n ず、これまでに出力された文字数をint変数に書き込むこと)を説明していますが、これまでのところ、実際に使用方法の例を示した人はいません。ここに1つあります:

int n;
printf("%s: %nFoo\n", "hello", &n);
printf("%*sBar\n", n, "");

印刷されます:

hello: Foo
       Bar

フーとバーを揃えて。(%nこの特定の例を使用せずにそれを行うのは簡単です。一般に、最初のprintf呼び出しを常に中断することができます:

int n = printf("%s: ", "hello");
printf("Foo\n");
printf("%*sBar\n", n, "");

少し追加された利便性が難解なものを使用する価値があるかどうか%n(そしておそらくエラーを導入するかどうか)は議論の余地があります。


3
ああ、これは与えられたフォントで文字列のピクセルサイズを計算する文字ベースのバージョンです!

&nと* sが必要な理由を説明してください。それらは両方ともポインタですか?
Andrew S

9
@AndrewS &nはポインタです(&アドレス演算子です)。Cは値渡しであり、ポインターprintfがないとの値を変更できなかったため、ポインターが必要ですn。フォーマット文字列%*sでの使用は、printf文字のフィールド幅を使用して%s指定子(この場合は空の文字列"")を出力します。基本的な原則の説明は、基本的にこの質問(および回答)の範囲外です。ドキュメントを読むか、SOについて独自の質問をすることをお勧めします。nprintfprintf
jamesdlin 2014年

3
ユースケースをご提示いただきありがとうございます。なぜ基本的にマニュアルをSOにコピーアンドペーストし、時々それを言い換えるのか、私にはわかりません。私たちは人間であり、すべてが答えで常に説明されるべき理由のために行われます。「何もしない」は「クールという言葉はクールを意味する」と言っているようなもので、ほとんど役に立たない知識です。
the_endian 2017年

1
@PSkocikこれは複雑で、エラーを起こしやすく、間接的なレベルを追加する必要がありません。
jamesdlin

18

私は実際に%n指定子の実際の使用を多く見たことはありませんが、かなり前からフォーマット文字列攻撃を伴う古い学校のprintfの脆弱性で使用されていたことを覚えています。

このようになった何か

void authorizeUser( char * username, char * password){

    ...code here setting authorized to false...
    printf(username);

    if ( authorized ) {
         giveControl(username);
    }
}

どこ悪意のあるユーザーがフォーマット文字列としてprintf関数に渡されたばかりのパラメータ名を利用しての組み合わせを使用することができ%d%cまたはW / Eは、コールスタックを経由して、真の値に許可変数を変更します。

ええ、それは難解な使用ですが、セキュリティホールを回避するためにデーモンを作成するときに知っておくと常に役に立ちますか?:D


1
%n未チェックの入力文字列をprintfフォーマット文字列として使用しないようにするよりも多くの理由があります。
キーストンプソン

13

ここから、これまでに印刷された文字数が格納されていることがわかります。

n 引数は、fprintf()関数へのこの呼び出しによってこれまでに出力に書き込まれたバイト数が書き込まれる整数へのポインターです。引数は変換されません。

使用例は次のとおりです。

int n_chars = 0;
printf("Hello, World%n", &n_chars);

n_charsその場合、値はになり12ます。


10

これまでのところ、すべての答えはそれについてです%nが、誰もが最初にそれを望んでいる理由ではありません。私はそれがsprintf/snprintf保存された値は結果の文字列への配列インデックスであるため、結果の文字列を後で分割または変更する必要がある場合に。ただし、このアプリケーションはを使用するとさらに便利にsscanfなります。特に、scanfファミリの関数は処理された文字数ではなくフィールド数を返すためです。

別の本当にハックな使い方は、別の操作の一部として数値を出力すると同時に、無料で疑似log10を取得することです。


+1はの使用法について言及したものですが%n、「すべての回答...」については違います。= P
jamesdlin 2010

1
悪者は、printf /%n、sprintf、およびsscanfを使用していただきありがとうございます;)
jww

6
@noloader:どうして?を使用し %nても、攻撃者に対する脆弱性の危険性はまったくありません。誤って配置された悪名は%n、フォーマット引数としてフォーマット文字列ではなくメッセージ文字列を渡すという愚かな習慣に本当に属しています。もちろん、この状況%nが実際に使用されている意図的なフォーマット文字列の一部である場合には決して発生しません。
R .. GitHub STOP HELPING ICE 2013

%nを使用すると、メモリに書き込むことができます。攻撃者がそのポインタを制御していないと思っていると思います(私は間違っている可能性があります)。攻撃者がポインター(printfへの別のパラメーター)を制御する場合、攻撃者は4バイトの書き込みを実行する可能性があります。彼/彼女が利益を得ることができるかどうかは別の話です。
jww

8
@noloader:ポインターの使用についてはそうです。「悪者に感謝」と書いた人は誰もいません*p = f();%nポインタ自体が危険であると見なすのではなく、ポインタが指すオブジェクトに結果を書き込むもう1つの方法であるを「危険」と見なす必要があるのはなぜですか?
R .. GitHub STOP HELPING ICE 2013

10

に関連付けられた引数は%nとして扱われ、のint*その時点で出力される合計文字数で埋められprintfます。


9

先日%n、問題をうまく解決できる状況に身を置きました。私の以前の回答とは異なりこの場合、私は良い代替案を考案することはできません。

指定したテキストを表示するGUIコントロールがあります。このコントロールは、そのテキストの一部を太字(または斜体、下線など)で表示できます。また、開始文字インデックスと終了文字インデックスを指定することで、どの部分を指定するかを指定できます。

私の場合、コントロールを使用してテキストを生成していますsnprintf。置換の1つを太字にしたいと思います。この置換の開始インデックスと終了インデックスを見つけることは簡単ではありません。

  • 文字列には複数の置換が含まれ、置換の1つは任意のユーザー指定のテキストです。これは、気になる置換のテキスト検索を実行することは潜在的にあいまいであることを意味します。

  • フォーマット文字列はローカライズされる場合があり$、位置フォーマット指定子にPOSIX拡張を使用する場合があります。したがって、元のフォーマット文字列でフォーマット指定子自体を検索することは簡単ではありません。

  • ローカリゼーションの側面は、フォーマット文字列をへの複数の呼び出しに簡単に分割できないことも意味しsnprintfます。

したがって、特定の置換に関するインデックスを見つける最も簡単な方法は、次のようにすることです。

char buf[256];
int start;
int end;

snprintf(buf, sizeof buf,
         "blah blah %s %f yada yada %n%s%n yakety yak",
         someUserSpecifiedString,
         someFloat,
         &start, boldString, &end);
control->set_text(buf);
control->set_bold(start, end);

ユースケースでは+1を差し上げます。ただし、監査に失敗するため、太字のテキストの開始と終了をマークする別の方法を考案する必要があります。書き込まれた文字数を返すsnprintfので、snprintf戻り値をチェックしながら3つは問題なく動作するようです。多分何かのように:int begin = snprintf(..., "blah blah %s %f yada yada", ...);int end = snprintf(..., "%s", ...);、その後、尾:snprintf(..., "blah blah");
jww

3
@jww複数のsnprintf呼び出しの問題は、置換が他のロケールで再配置される可能性があるため、そのように分割できないことです。
jamesdlin 2017年

例をありがとう。しかし、ターミナルコントロールシーケンスを記述して、出力をフィールドの直前で太字にし、その後にシーケンスを記述できませんか?端末制御シーケンスをハードコーディングしない場合は、それらを定位置(再配列可能)にすることもできます。
PSkocik

1
@PSkocik 端末に出力する場合。たとえば、Win32リッチエディットコントロールを使用している場合、後で戻ってターミナルコントロールシーケンスを解析する必要がない限り、これは役に立ちません。また、置換されたテキストの残りの部分で端末制御シーケンスを尊重することを想定しています。そうしないと、それらをフィルタリングまたはエスケープする必要があります。なしでは不可能だと言っているのではありません%n。使用する%n方が他の方法よりも簡単であると私は主張しています。
jamesdlin

1

何も印刷されません。これは%n、フォーマット文字列に現れる前に印刷された文字数を把握し、それを提供されたintに出力するために使用されます。

#include <stdio.h>

int main(int argc, char* argv[])
{
    int resultOfNSpecifier = 0;
    _set_printf_count_output(1); /* Required in visual studio */
    printf("Some format string%n\n", &resultOfNSpecifier);
    printf("Count of chars before the %%n: %d\n", resultOfNSpecifier);
    return 0;
}

(のドキュメント_set_printf_count_output


0

そのprintf()関数でこれまでに印刷された文字数の値を格納します。

例:

int a;
printf("Hello World %n \n", &a);
printf("Characters printed so far = %d",a);

このプログラムの出力は

Hello World
Characters printed so far = 12

コードを試してみると、次のような結果が得られます。これまでに印刷されたHello World文字= 36 、、、、、なぜ36ですか。Windowsマシンで32ビットGCCを使用しています。
Sina Karvandi 2016年

-6

%nはC99であり、VC ++では機能しません。


2
%nC89に存在した。Microsoftはセキュリティ上の理由からデフォルトでMSVCを無効にしているため、MSVCでは機能しません。_set_printf_count_output有効にするには、最初に呼び出す必要があります。(Merlyn Morgan-Grahamの回答を参照してください。)
jamesdlin 2010

いいえ、C89はこの機能/バックドアバグを定義していません。K&R + ANSI-Cを参照amazon.com/Programming-Language-2nd-Brian-Kernighan/dp/...コメントのURL、タグ付けされ???
user411313

4
あなたは単に間違っています。printfK&R、第2版の付録Bの表B-1(変換)に明記されています。(私のコピーの244ページ。)または、ISO C90仕様のセクション7.9.6.1(134ページ)を参照してください。
jamesdlin 2010

Androidは%n指定子も削除しました。
jww 2013

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.