質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Q&A

解決済

5回答

539閲覧

printf関数に関する疑問

vinegar1217

総合スコア22

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

0グッド

3クリップ

投稿2017/07/20 11:58

###前提・実現したいこと
c言語の初心者です。printf関数について質問です。

###発生している問題・エラーメッセージ
以下のコードを実行した際想像とは違った結果になったため質問させていただきます

###該当のソースコード

#include<stdio.h> main() { double pi = 3.14; int test = 10; printf("pi=3.14を整数型で出力\t%d\n", pi); printf("test=10を小数型で出力\t%f\n", test); return 0; }

###実行結果
pi=3.14を整数型で出力 1374389535
test=10を小数型で出力 0.000000

###想像していた実行結果
pi=3.14を整数型で出力 3
test=10を小数型で出力 10.000000

1行目:小数を整数で出力すると小数点以下が切り捨てられると考え3が出力されると予想しましたが実際は意味不明な数字が出力されました。
2行目:整数を小数で出力すると小数点以下に0が付け足されて出力されると予想しましたが実際は0になりました。
以上2点について解説をお願いします。
なお実行はVisual Studio 2017をを用いました。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答5

0

ベストアンサー

コンピュータはどんなデータも2進数で扱います。まずは、int型、double型の数値が、どのような形式でメモリに格納されるか、その違いをご理解ください。

int型は32bit(と、後述のように、表示結果から推測可能)なので、10 = 0x0000000a です(2進数の値は16進数で扱うのが現実的)。
一方、double型、即ち倍精度浮動小数点数の数値は64ビットの2進数です。
倍精度浮動小数点数の形式

intとdoubleでは、ビット数の違い(32bitと64bit)もさることながら、その中の各ビットの意味がまるで違う。この事を覚えておくと良いです。

pi=3.14を整数型で出力 1374389535

調べれば、3.14 は 0x40091eb851eb851f であることがわかります。もうひとつ、表示された 1374389535 という10進数を16進数で表わせば 0x51eb851f です。Windowsの電卓ユーティリティで、貴方もすぐ確認可能です。

見比べてください。1374389535 は、3.14 を2進数で表した64ビットの下位32ビットを、int型のデータとみなし10進数で表示した結果だとわかります。

test=10を小数型で出力 0.000000

printf() に渡されたのは 0x0000000a という32bitです。printf()が、%fという変換指定に従い、メモリ上のデータを見たら、おそらく(たまたま?) 0x000000000000000a だったのではないでしょうか。その64bitの値を倍精度浮動小数点数とみなした結果が 0.000000 でしょう。

printf関数は指定された書式に従ってメモリの内容を出力する動作

と、hmmmさんがズバリおっしゃった通りです。

このように、一見不可解に見える挙動は、データがどのような形でメモリに格納されているか、メモリ上の2進数値を調べると解決することが多くあります。

明示的キャスト・暗黙的キャストについて。
型の異なるもの同士の代入など、例えば test = pi; のような場合は、コンパイラが気を効かせて(笑)、型変換の処理をしてから代入してくれます。明示的なキャストをすれば、勿論そこに必要な型変換処理をしてくれます。

しかし、「メモリの内容を出力する」printf() に渡される引数が、暗黙的にキャストされることはありません。 printf("pi=3.14を整数型で出力\t%d\n", (int)pi);の場合は、浮動小数点数を整数に変換した結果(即ち 0x00000003)が printf() に渡り、3 と表示されます。
こうした事は、コンパイル結果をアセンブリコードで確認すると明確になります。

追記:浮動小数点数が、どのような2進数になるのか、unionな変数を使うと容易に確認できます。

C

1 union { 2 double dval; 3 int ival[2]; 4 } upi; 5 6 upi.dval = 3.14; 7 printf("dvalを16進数で表示\t%x %x\n", upi.ival[1], upi.ival[0]);

実行結果です。
dvalを16進数で表示 40091eb8 51eb851f

念の為:printf("%x %x\n", upi.ival[1], upi.ival[0]);という順序で表示する理由は、私達が使うパソコンのCPUが、リトルエンディアンでメモリをアクセスするからです。

投稿2017/07/20 23:20

編集2017/07/21 02:07
rubato6809

総合スコア1380

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

rubato6809

2017/07/21 05:33

> printf() に渡される引数が、暗黙的にキャストされることはありません おっと、これは不正確です。すみません。 float の値がprintf()の引数にある場合は、暗黙の型変換が行われ、doubleに格上げした値がprintf() に渡されるのでした。これはK&R時代もそうでした。
vinegar1217

2017/07/21 21:22

丁寧な回答ありがとうございました。
guest

0

printf関数は指定された書式に従ってメモリの内容を出力する動作になります。
ですので、イメージとしては以下のようにdoubleのメモリ内容をそのままint型の変数にコピーしてprintfした結果と同じになります。
vinegar1217さんの期待する結果を得たい場合はキャストする必要があります。

c++

1#include<stdio.h> 2 3int main() 4{ 5 double pi = 3.14; 6 int test = 10; 7 int pi_as_int = 0; 8 float test_as_float = 0; 9 memcpy(&pi_as_int, &pi, sizeof(pi_as_int));//メモリの内容をそのままコピー 10 memcpy(&test_as_float, &test, sizeof(test_as_float));//メモリの内容をそのままコピー 11 printf("pi=3.14を整数型で出力\t%d\n", pi); 12 printf("pi_as_intを整数型で出力\t%d\n", pi_as_int); 13 printf("test=10を小数型で出力\t%f\n", test); 14 printf("test_as_floatを小数型で出力\t%f\n", test_as_float); 15 printf("test_as_floatを小数型で出力\t%g\n", test_as_float); 16 17 printf("pi=3.14を整数型で出力\t%d\n", (int)pi);//キャストして出力 18 printf("test=10を小数型で出力\t%f\n", (float)test);//キャストして出力 19 return 0; 20}

pi=3.14を整数型で出力 1374389535

pi_as_intを整数型で出力 1374389535
test=10を小数型で出力 0.000000
test_as_floatを小数型で出力 0.000000
test_as_floatを小数型で出力 1.4013e-44
pi=3.14を整数型で出力 3
test=10を小数型で出力 10.000000

投稿2017/07/20 14:55

hmmm

総合スコア818

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

printf関数は引数をいくつでもとることができる特殊な関数です(可変長引数)。
printfは、

文字1個、整数2個、浮動小数点数3個、文字と整数1個ずつ、…

など、多種多様な引数をとることができます。これらすべてを含むことが可能な型は、ありません。なのでプロトタイプ宣言においては、引数とする型を明記しません。

int printf(const char*, ...);

では、関数を記述する側では、どのように引数を受けとるのか。printfの場合、最初の引数で、後続の引数がどのようになっているかを指定します。%dなら、int型、%fなら、doubleのように。

しかし、それはあくまで実装の話。利用側では、最初の引数に何が書いてあろうが、piならdouble型、testならint型を渡すようにコンパイルされます。

おかしな挙動になったのは、書式指定が誤っていたために、渡された変数の情報を正しく解釈できなかったからです。正しく解釈させたければ、書式指定に合うように明示的にキャストする必要があります。

以上、非常にかいつまんだおはなし。不正確な部分もありますが参考になりましたか?

参考文献:
http://hitorilife.com/verargs.php

投稿2017/07/20 13:33

majiponi

総合スコア1720

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

コンパイルオプションで、printfの引数も見てくれるものがあったはず。
/Wall だったかな。

投稿2017/07/21 06:43

Youichi256

総合スコア204

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

#include<stdio.h> main() { double pi = 3.14; int test = 10; printf("pi=3.14を整数型で出力\t%d\n", (int)pi); printf("test=10を小数型で出力\t%lf\n", (double)test); return 0; }

これで、
pi=3.14を整数型で出力 3
test=10を小数型で出力 10.000000

お望みの結果が得られます。

暗黙的なキャストには、他の変数に代入した時に一定の規則の元値が変化します。
ええ〜〜と。
すぐには確認できないんですが、確か符号付き整数と符号なし整数の暗黙的キャストが一番複雑で、数式があったはずです。

すいません。間違っているかもしれません。

ただ暗黙的なキャストか明示的なキャストかの違いかと思います。

投稿2017/07/20 12:35

編集2017/07/20 12:44
strike1217

総合スコア651

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

LouiS0616

2017/07/20 13:01

暗黙的にキャスト出来ていないから、値が壊れているんじゃないでしょうか...?
strike1217

2017/07/20 13:17

私はLinuxでやったのですが、実行するたびに値が変動したりはしませんでした。 つまり、ある規則に則っていると言えるはずです。 値が壊れている・・・とはどういうことか分からないのですが、実行するたびに値が変動していたら・・・どうなんでしょう・・・「値が壊れている」と言えるんですかね?
strike1217

2017/07/20 13:26 編集

https://teratail.com/questions/62786 こちらをどうぞ! ポインタに限った話ではないと思いますが、「暗黙的キャストは避けるべきコーディングですよ。」と仰られています。 私の説明力では、暗黙的キャストの規則性をここで説明するのは難儀なのですが・・・処理系依存の場合もあります。
strike1217

2017/07/20 13:30

vinegar1217さんのコードはwarningが出てきませんでしたか? Linuxでは出現しました。
LouiS0616

2017/07/20 13:33

int i_pi = pi; double d_test = test; printf("%d, %f\n", i_pi, d_test); こういうのを暗黙的なキャストと言うんではないでしょうか?
vinegar1217

2017/07/20 13:57

回答ありがとうございます。 型の異なる計算(int型+double型など)や出力(printf("%d",double型)など)を行おうとしたときパソコンが勝手に型を合わせることを暗黙的キャストといい、上手くいくこともあるが今回のように正しい結果が得られないことがあるため、基本的にキャストは明示的に行ったほうが良い。 このように理解したのですが何か間違いはありますか? エラーに関しては見落としていましたが確かに表示されていました。 エラー表示:warning C4477: 'printf' : 書式文字列 '%d' には、型 'int' の引数が必要ですが、可変個引数 1 は型 'double' です warning C4477: 'printf' : 書式文字列 '%f' には、型 'double' の引数が必要ですが、可変個引数 1 は型 'int' です
yuki23

2017/07/20 14:28

vinegar1217さん 「キャストは明示的に行ったほうが良い」というところは正しいですが、今回の問題は暗黙的キャストが行われることが原因ではありません。むしろ、暗黙的キャストが行われていないことが原因です。 詳しくは majiponi さんの回答を参照ください。
strike1217

2017/07/21 02:39

私の回答間違っているようですね。 すいません。お手数をおかけしました。
strike1217

2017/07/21 08:34

「暗黙的キャスト」は必ずしも期待通りの結果にキャストされるとは限らない・・・・はずです・・・ 反対意見が多いので自信がありません。 すいません>< 勉強不足ですね。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問