アプリ版:「スタンプのみでお礼する」機能のリリースについて

はじめまして。最近趣味でプログラムを始めました。

参考書を見ながら、printfを使って「3-2は1です。」と表示しようとしました。
見ながら打ち込んで行ったのですが、

printf("%d-%dは%dです。", 3, 2, 3-2 ); を
printf("%d-%dは%dです。")のまま、コンパイルしてしまったのですが
計算結果は
「1638280-4198563は1です。」との結果がでました。
これは何が表示されたのでしょうか?

「%d-%dは%dです。」と同じ書式設定の物を打ち込んでいるのになぜ違う
数字の羅列が出たのでしょうか?

参考書のプログラムは下記の通りでした。
#include <stdio.h>

int main(void)
{
printf("%d-%dは%dです。", 3, 2, 3-2 );
return 0;
}

どうぞご回答よろしくお願いします。

A 回答 (4件)

ちょっと専門的な話になります。



関数を呼び出すとき、内部では
引数1: XXXX
引数2: YYYY
引数3: ZZZZ
....
という引数を渡すための領域を用意して、関数を呼び出します。
関数側では、この領域から引数を得ます。
int func(int a,int b)
という関数だったら、引数は2個なので
z=func(101,102)
とあったら
引数1: 101
引数2: 102
という領域を用意して、関数側では
a←引数1(101)
b←引数2(102)
となります。


ところが、printf の様に引数の数が一定でない関数の場合、引数がいくつ渡されたかが「わかりません」
というのがC言語の仕様です。

引数の数は、何か別の方法で知らせる必要があります。
 引数に「引数の数」を入れる
  func( 3,101,102,103) /* 最初の3は引数の数 */
 特定の引数を「これが最後」という意味に使う
  func( 101,102,103, -1) /* -1 が来たら終わり */

printfの場合は、「書式文字列を解析する」ことで、必要な引数を知ります。

printf("%d-%dは%dです。", 3, 2, 3-2 );
だと
引数1: "%d-%dは%dです。" (へのポインタ)
引数2: 3
引数3: 2
引数4: 1 (3-2の計算は、関数に渡す前に計算される)
となります。

このとき、printf側では
・引数1の書式文字列を得る
・書式文字列を解析する
 → %d が3つあるから、 intが3つ必要
・1つ目のintとして 引数2 を得る
・2つ目のintとして 引数3 を得る
・3つ目のintとして 引数4 を得る
と動作します。

では
printf("%d-%dは%dです。" );
だとどうなるか?

引数1: "%d-%dは%dです。" (へのポインタ)
はいいでしょう。
このとき、printf側では
・引数1の書式文字列を得る
・書式文字列を解析する
 → %d が3つあるから、 intが3つ必要
・1つ目のintとして 引数2(!?) を得る
・2つ目のintとして 引数3(!?) を得る
・3つ目のintとして 引数4(!?) を得る
と動作します。

引数2、3、4は用意されていないのに、そこから値を得ています。
用意されていない領域でも、アクセスしようとしたらエラーにならずにアクセスできる。
これもC言語の特徴です。
/* その領域がOSの保護領域等の場合は、エラーになることがあります。
 ただ、これは、OSが発生させるエラーであって、C言語としてのエラーではありません */

このとき、引数2、3、4には何が入っているかわかりません(不定)
前に別な関数を使ったときの残りかもしれないし、何かの変数が使っている領域かもしれません。
今回はたまたま
引数2: 1638280
引数3: 4198563
引数4: 1 (これはたまたま1だった、というだけで、3-2が入っていたわけではない)
だった、ということです。

これを完全に防ぐ方法は「ありません」。
プログラマが気をつける以外に方法はありません。
一部Cコンパイラには、書式文字列と引数の数を比較して、数や型に不一致があると「警告」を出すものもあります。
/* gcc に -Wall オプションを付けるとか */
ですが、これも printf(s,1,2,3) 等と変数を使われたら対応できません。



余談ですが。
printf("%d-%dは%dです。", 3.0, 2.0, 3.0-2.0 );
も期待する出力にはならないでしょう。
実は、数だけではなく、引数の型もわかりません。
そのため、%d→int と決め付けて値を得ます。
このとき 3.0→3と変換されるのではなく、「 3.0の内部のビット表現→それと同じビット表現のint 」と変換されるため、「3」とはまったく別の値になります。
    • good
    • 1
この回答へのお礼

丁寧な解説ありがとうございました。
知識の浅い私にも何とか理解できそうです。
ご回答をいただくまでいろんなC言語の解説サイトを見ましたが、上記に触れているところはありませんでした。
どこの入門サイトにも最初に触れるprintfなので、どこかしら解説されていそうですがどこもスルーされているようです。
皆さんエラー内容に関しては特にモヤモヤしたりしないんでしょうか?わたしはモヤモヤしっぱなしだったんですが・・・

お礼日時:2015/07/13 18:21

プログラムから呼び出されるサブルーチン printf に必要な、レジスタの初期化処理が不足しているからです。


特殊呼び出しとなる printf は、一般にC言語コンパイルラではうまく検証できないので、この様な泥臭い問題が発生します。

---- printf("%dです。", 123) をコンパイルした機械語イメージ
mov レジスタ2, 前の処理に使った値
...
mov レジスタ2, 123 ← レジスタの値を初期化
mov レジスタ1, 書式文字列のアドレス
call printf ← レジスタ1 の書式に%dがあるので、レジスタ2 から「123」を読み取って、整数として表示

---- printf("%dです。") をコンパイルした機械語イメージ
mov レジスタ2, 前の処理に使った値
...
mov レジスタ1, 書式文字列のアドレス
call printf ← レジスタ1 の書式に%dがあるので、レジスタ2 にたまたま設定してあった「前の処理に使った値」を読み取って、整数として表示
    • good
    • 0
この回答へのお礼

かなり踏み込んだ解説どうもありがとうございました。

お礼日時:2015/07/13 18:11

規格上, printf などで第1引数に与える書式に対して, そのあとの引数列が足りないあるいは型が矛盾する場合には


未定義動作
となっています.

まあたいていは今の例のように何かしらのゴミが表示されるでしょうが, たとえ
・突然鼻歌を歌いだす
・なぜか自爆する
などとということが起きたとしても「未定義動作」である以上文句は言えません.
    • good
    • 0
この回答へのお礼

わかりやすい例えでの解説ありがとうございます。
私が「旦那にラーメン食いたいから箸を取って欲しいと命令したら、ボールペン2本(箸ではないが取り敢えず箸と認識できそうなもの)を持ってきた」みたいな解釈でよろしいのでしょうか?

お礼日時:2015/07/13 18:14

かなりの確率で、


printf("%d-%dは%dです。", 3, 2, 3-2 ); の「3、かんま、2」が
printf("%d-%dは%dです。", 3.2, 3-2 ); の「3、てん、2」に
なっているのではないかと思います。
    • good
    • 0
この回答へのお礼

親切な回答ありがとうございます。
私も最初はそうかなと思い、何度も入力内容を確認しましたが「、」ではありませんでした。
これからも勉強をしていきますので、こういった初歩的なミスをしないようにしていきたいと思います。

お礼日時:2015/07/13 18:24

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!