関数printfはCでどのように機能しますか?

関数printfをテストすると問題が発生しました。

最初にこのようなコードを書きます。

int main(void)
{
    char a = 'a';
    printf("a = %f\n", a);
    return 0;
}

出力は

enter image description here

それから私はコードを書く:

int main(void)
{
    float b = 'a';
    printf("b = %f\n", b);
    return 0;
}

出力は

enter image description here

それから私はコードを書く:

int main(void)
{
    char a = 'a';
    float b = 'a';
    printf("b = %f\n", b);
    printf("a = %f\n", a);
    return 0;
}

出力は

enter image description here

では、なぜ最初のプログラムで a = 0.000000 そして3番目のプログラムで a = 97.000000 なのでしょうか。 br> printf()関数はどのように機能しますか。
シンボル%f%d はどのように機能しますか?

5
RTFM、 printf(3)または printf
追加された 著者 Basile Starynkevitch,
あなたはprintfについて混乱していますが、 float b = 'a'; は問題ありませんか? ;)
追加された 著者 sebastian,
@ sebastianすみません、私はあなたが何を意味するのかわからない。
追加された 著者 Mr.CodeMonkey,

6 答え

Update: after doing some more research on this, it seems that the differences between the float and int memory representations are not the ones responsible for the behaviour of the three programs.

私は3番目のプログラムのオブジェクトコードを調べて、私は奇妙な振る舞いの原因を見つけました。浮動小数点引数は整数のもの以外の他のレジストリ/スタックに送られます。そして printf はそれを当てにして printf の呼び出し先(すなわち main メソッド)が引数を置く場所以外の場所でそれらを調べます。

これが3番目のプログラムの関連する逆アセンブリです(x86_64アーキテクチャ用)。

0000000100000f18    leaq    0x71(%rip), %rdi        ## literal pool for: "b = %f\n"
0000000100000f1f    movsd   0x61(%rip), %xmm0      ## b variable gets sent to xmm0
0000000100000f27    movl    $0x0, -0x4(%rbp)
0000000100000f2e    movb    $0x61, -0x5(%rbp)      ## a variable gets placed on the callee stack
0000000100000f32    movsd   %xmm0, -0x10(%rbp)
0000000100000f37    movsd   -0x10(%rbp), %xmm0
0000000100000f3c    movb    $0x1, %al
0000000100000f3e    callq   0x100000f66             ## symbol stub for: _printf
0000000100000f43    leaq    0x4e(%rip), %rdi        ## literal pool for: "a = %f\n"
0000000100000f4a    movsbl  -0x5(%rbp), %esi
0000000100000f4e    movl    %eax, -0x14(%rbp)
0000000100000f51    movb    $0x0, %al
0000000100000f53    callq   0x100000f66             ## symbol stub for: _printf

そして printf はこれを当てにしており、呼び出し先が%f 引数を xmm0 / xmm1 /etcに置いていると仮定しています。 3つのプログラムの動作は次のとおりです。

    最初のプログラムの
  1. printf では、xmm0レジスタの%f 引数を探しますが、プログラムの最初の部分にあるので、レジスタはきれいで、 mainaeax に配置したため、 xmm0 はゼロの値を保持します。これが printfです。 プリント
  2. 2番目のプログラムの
  3. main では bxmm0 に正しく配置され、そこから printf が出力されます正しい値
  4. 3番目のプログラムでは、 b が最初に印刷されるため、 xmm0 レジスタにこの値が格納されます。 printf 以降は無効です。最初の printf 呼び出しの後も元のまま残っている xmm0 から2回目に呼び出されたときに、レジスタに煩わされないでください。

So it's all about caller/callee conventions on where integers and floats are being send by the caller and from where the callee tries to pick them up.


Original response: In the first program you are trying to print a float, but you pass an int (char is a smaller int). Due to the fact that ints and floats have different binary representations, the int 97 (corresponding to the character 'a') corresponds to a very small float: 1.36E-43, that gets printed as zero.

Here is the binary representation of 97 (the compiler expands any 1-byte char to a 4-byte argument when calling a function)
00000000 00000000 00000000 01100001

IEEE 754 is the standard format for binary representations of float/double numbers, you can play with an online converter here, and you can see how the same binary number has different values when its interpreted as an int or as a float.

6
追加された
これは、フォーマット文字列とprintfに渡される引数のリストが一致しない場合に起こる、未定義の動作の1つです。クラッシュしなかったことを嬉しく思います:)
追加された 著者 Cristik,
ご回答ありがとうございます。しかし、それはどうやって3番目のプログラムを説明できるのでしょうか。
追加された 著者 Mr.CodeMonkey,
私はそれが3番目のプログラムの出力につながる出力バッファだと思います。 2つのprintf文を交換すると、出力がa = 0.000000 b = 97.000000になったためです。
追加された 著者 Mr.CodeMonkey,

ここでの%f はフロートの代替トークンを表します。

文字を置き換えるには、%c が必要です。

Here is a list that tells you what is the appropriate replacement token for each type.

5
追加された
@ Mr.CodeMonkey、あなたがしていることは未定義の振る舞いなので、すべては宇宙の完全な破壊を含む有効な結果です。言い換えれば、それをしないでください:-)
追加された 著者 paxdiablo,
私はこれを知っていますか。しかし、いつ%cの代わりに%fを使うのか知りたいのですが、関数printfは何をするのでしょうか。それはどのようにレジスタを読みますか?
追加された 著者 Mr.CodeMonkey,

最新の 'C'標準によれば、それは未定義の動作です。 C標準ドラフトの7.21.6.1 pt 9を確認してください。

変換指定が無効な場合の動作は   undefined.282)引数が正しい型ではない場合   対応する変換仕様、動作は未定義です。

そのため、未定義の動作があると言われた場合は何でも可能であり、動作はコンパイラによって異なります。 'C'つま先を斧で切るようにしましょう。

1
追加された

%fはフロート用です  文字の場合は%c

The 97 which you have got is the ASCII value for 'a'
1
追加された

との差

printf("%f\n, 97.0);

そして

printf("%c\n, 'a');

is that the printf function reads its parameters from the stack based on the %X you give, そして interprets them (for display) as such.

For %c printf expects a char as parameter, so it will read a char (a byte, but often actually a int, it's implementation dependant) そして displays it (it displays the less significant byte if an int is provided).

%f の場合、printfはfloat(バイト単位のサイズは sizeof(float)で、通常はgcc/Intelプロセッサでは4バイトです)。

If you compile with gcc use the -Wall option that would give a warning when the %X format そして the type of the parameter do not match.

0
追加された
なぜこのような場合に 0 が返されるのかを説明しているCristikの優れた答え(上記のとおり)を読んでください。
追加された 著者 Ring Ø,
私はあなたが言ったことを理解しています。しかし、それは私のプログラムを説明することはできません。 charのように定義するとa = 'a'; printf( "a =%f \ n"、a); printfはaのアドレスから始まる4バイトのデータを読み取りますか?もしそうなら、出力は0になることはできませんね。
追加された 著者 Mr.CodeMonkey,

%f is for float. You must use %c for characters.

使うなら

    printf("a = %c\n", a);

あなたはキャラクターを手に入れるでしょう。

したがって、最初のコードを次のように変更したとします。

int main(void)
{
    char a = 'a';
    printf("a = %c\n", a);
    return 0;
}

次のように出力されます

a
0
追加された
@ Mr.CodeMonkey、こちらの出力を確認してください
追加された 著者 Arun A S,
@ Mr.CodeMonkey、出力の違いはおそらくコンパイラがそれをどのように解釈するかによるものです。
追加された 著者 Arun A S,
知っている。しかし、私は%fと%cの違いを知りたいですか?
追加された 著者 Mr.CodeMonkey,
なぜああ?私はclodeblocks 13.12を使用しています。私のOSはwindows 7 64bitです。私たちのアウトプットが違う理由をあなたは知っていますか?
追加された 著者 Mr.CodeMonkey,
初期化されていないメモリのデータに依存すると思います。
追加された 著者 Mr.CodeMonkey,