2015-11-13 6 views
5

Cでは、このprintf行を別の行に変更すると、printf("%f\n", 5/2);の出力が変更されます。何か案は?printfを異なる行に移動すると出力が異なりますか? (C)

HERESにコード:

#include <stdlib.h> 
#include <stdio.h> 


int main() 
{ 

    int a = 65; 
    char c = (char)a; 
    int m = 3.0/2; 

    printf("%c\n", c);    
    printf("%f\n", (float)a);  
    printf("%f\n", 5.0/2);   
    printf("%f\n", 5/2.0);   
    printf("%f\n", (float)5/2); 
    printf("%f\n", 5/(float)2); 
    printf("%f\n", (float)(5/2)); 
    printf("%f\n", 5.0/2);   
    printf("%d\n", m);    
    printf("%f\n", 5/2); 


    system("PAUSE"); 
    return(0); 
} 

そして相続人出力:

A 
65.000000 
2.500000 
2.500000 
2.500000 
2.500000 
2.000000 
2.500000 
1 
2.500000 

私はAを出力1と出力1との間の最初の行(のいずれかにprintf("%f\n", 5/2);を移動する場合65.000000)それは現在の2.500000の代わりに0.000000(それは理にかなっています)を出力します。 アイデア

+2

'printfの( "%fが\ nを"、5月2日);'利回り未定義の振る舞い。 (整数)除算 '5/2'の結果は、整数オペランドであり、値は '2'である。コンパイラが 'printf'に渡す前に' double'に展開するという事実は言うまでもなく、 'printf'はスタックから8バイトのデータを読み込もうとしますが、データの 'sizeof(int)'バイトだけを保持します)。 –

+1

'%f'で整数を出力することはできません。 – Lundin

+3

printfに関するC99標準に従う "対応する変換仕様の引数が正しくない場合、その動作は未定義です。" (WG14 N1570,7.161.1/p9)。 –

答えて

4

コードで未定義の動作が呼び出されています。

printfのものを印刷するための正しいデータ指定子を使用することは義務付けられていますが、そうしないとUBが呼び出されます。したがって、それは重要ではなく、異なる場所で異なる結果を得ることは驚くことではありません。

http://en.cppreference.com/w/c/io/fprintf

変換指定が無効である場合、動作は未定義です。

ちょうどcの場合も同様です。

定義されていない動作を呼び出すとき、結果はランダムに定義され、予測できないため、予測することはほとんど意味がありません。

3

コメント者が示したように、printf("%f\n", 5/2);の行は、定義されていない動作を単に示します。しかし、System V ABIを使用してx86-64アーキテクチャでこれらのような結果を得る理由を見てみましょう。

簡単な答えは、最初のいくつかの引数がレジスタを介して通信されることです。選択肢は引数の型に依存します。整数引数は "古典的"レジスタ(ediesiなど)、浮動小数点はSSEレジスタ(xmm0xmm1など)に入ります。

フォーマット文字列に間違った型を指定しているので、printfは間違ったレジスタから引数を読み込んでいます。


のは、次のようにあなたのプログラムを簡素化してみましょう:

#include <stdio.h> 

int main(void) 
{ 
    printf("%f\n", 5/2); 
    printf("%f\n", 5.0/2); 
    printf("%f\n", 5/2); 

    return 0; 
} 

は、今度はmainの解体を見てみましょう。我々はあまりにも特別なものではありません関数プロローグとオフを開始:次に

push %rbp 
    mov %rsp,%rbp 
    sub $0x10,%rsp 

、我々は(フォーマット文字列へのポインタを取得します)引数がediに渡されprintfへの私たちの最初の呼び出しを取得し、 esi5/2、除算を整数に起因2である):

mov $0x2,%esi 
    mov $0x4005e4,%edi 
    mov $0x0,%eax 
    callq 4003e0 <[email protected]> 

しかし、printf"%f\n"フォーマットを読み、xmm0から引数を読み取るしようとします。私の場合、このレジスタの値は0なので、これは0.000000を出力します。今

movabs $0x4004000000000000,%rax 
    mov %rax,-0x8(%rbp) 
    movsd -0x8(%rbp),%xmm0 
    mov $0x4005e4,%edi 
    mov $0x1,%eax 
    callq 4003e0 <[email protected]> 

、あなたがある0x4004000000000000、としてここを参照してください期待2.500000うちprintfプリント(:2番目の呼び出しで

、引数は明らかにxmm0を通じて渡される浮動小数点数であり、 2.5の64ビット浮動小数点定数のようなものです)。 xmm0に渡し、xmm0から読み取ります。

第三の呼び出しは正確に最初のと同じです。

mov $0x2,%esi 
    mov $0x4005e4,%edi 
    mov $0x0,%eax 
    callq 4003e0 <[email protected]> 

は何が変わったのはprintfへの呼び出しがxmm0で値を変更しなかったことです。これは、2回目の呼び出しの前に定数2.5が含まれており、3回目の呼び出しでprintfと呼びます。 3回目の呼び出しでは、printf2.500000を再度印刷します。

(そして、我々の機能はもちろん、退屈なreturn 0で終わる:)

mov $0x0,%eax 
    leaveq 
    retq 
+0

コンパイラが 'eax'に対して行っていることは、ここの脚注よりもはるかに価値がありません.SysV x86-64 ABIでは、' eax'は 'printf'などの可変関数の引数リストに関係しています。理論的には、 'printf'は引数に何か問題があることを検出できました(UB呼び出しでは' eax = 0'、正しい呼び出しでは 'eax = 1'を取得します)。 ''がこの区別をしないので、実際には 'printf'実装はこれに気づきません。 –

関連する問題