こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

[C言語]puts()について

危ない書き方ですが

gets(&a);
puts();

と書くと

gets(&a);
puts(&a);

と同じ働きをするのは何故ですか?
そもそもputsが何故引数なしで動くのかが分かりません。

投稿日時 - 2012-02-13 15:21:33

QNo.7302776

暇なときに回答ください

質問者が選んだベストアンサー

たとえば、手元にある、C++ Bulder XE では、同じようには動きません。

このあたりは、実装によりけりで、関数呼び出しの際に、

・呼び出し側が、引数をスタックに積んで、
・関数呼び出しを行い(このときに、リターンアドレスが、さらに、スタックに積まれる)
・関数側では、リターンアドレスがスタックに積んであることを前提に、その下にあるはずの、
 引数を使用して、
・処理が終わったら、スタックトップにあるアドレス(リターンアドレス)に復帰して、
・呼び出し側が、スタックに積んであるはずの引数を解放する

という動作をする処理系では、gets() で渡された引数領域は、puts() を(引数なしで)呼び出したときのリターンアドレスで上書きされますから、跡形もなく消えてしまいます。

世に中には、引数が少ないときには、レジスタを介して引数を渡すというような処理をするコンパイラも(もしかしたら、そういうオプションも)ありますから、そういうレジスタ渡しをするものだと、質問されたような動作をするかもしれません。

というのも、まず、gets() というのは、返値を持ちます。
正常に動作した場合、gets(buff) と呼び出した返値は、a になります。
だから、puts(gets(buff)); は正しい動作をします。

さて、レジスタ渡しをする処理系では、多くの場合、

・最初の引数は、a レジスタ(一番汎用的そうなレジスタ)で渡し、
・関数の返値は、a レジスタ(〃)で返す

という動作をします。
この動作を仮定すると、

puts(buff); から処理が帰ってきた直後、a レジスタには、buff の値が入っていることになります。
この後、
puts(); と引数なしで呼ぶと、a レジスタには、何もセットされませんが、直前の処理の結果として、buff の値がそのまま残っています。
puts() は、この値を引数として渡されたと信じて処理を行います。
このために、buff の内容が出力されることになります。

このように、そのコンパイラが何をやっているかで、どういう動作をするかわからないということになります。また、引数が一致しなくて、暴走してもいいわけです。なので、未定義動作であるわけです。

さて、もとも、Cの動作自体は、

・呼び出し側は、ソースに書かれている引数リストを関数に渡す
・呼ばれた側は、自分が理解している引数が渡されたと信じて、処理を行う

という動作をしますから、引数を間違えても、動いてしまいます(正しく動くとは限らない)
それでは危険だから、プロトタイプを使って、引数のチェックをするのが正しいわけです。

投稿日時 - 2012-02-13 17:56:17

お礼

コンパイラはgccを使用しております。
詳しい解説ありがとうございます。
getsの返り値をそのままputsが引数として使ってる可能性があるんですね。

投稿日時 - 2012-02-15 05:05:20

このQ&Aは役に立ちましたか?

0人が「このQ&Aが役に立った」と投票しています

回答(5)

ANo.5

他の方も書かれていますが、たまたま正常に動いてるように見えてるだけです。

投稿日時 - 2012-02-13 22:25:02

お礼

回答ありがとうございます。

投稿日時 - 2012-02-15 05:06:14

ANo.4

No.3 ひとつ訂正です。


> 正常に動作した場合、gets(buff) と呼び出した返値は、a になります。
> だから、puts(gets(buff)); は正しい動作をします。

これ、


正常に動作した場合、gets(buff) と呼び出した返値は、buff になります。
だから、puts(gets(buff)); は正しい動作をします。

です。
gets() の引数を a にすると、a レジスタと紛らわしいなと、途中で buff に書き換えたら、a のまま残っていましたね。

投稿日時 - 2012-02-13 17:59:19

ANo.2

>同じ働きをするのは何故ですか?

偶然です。

「引数の実体」ってのは「スタックエリア」と言う場所に格納され、呼ばれた先で「ここにある筈」として使われます。

gets(&a);



puts(&a);

の2行で「偶然、引数が同じ場所に格納されて関数が呼ばれる状況にあった」ので、呼ばれた側でも、同じ場所から引数を取り出します。

その時、putsの方で

puts();

と書くと「格納する筈の引数を格納しない」でputsを呼んでしまいます。すると、呼ばれた側では「getsを呼んだときに使った引数の残骸を、引数だと思って取り出して使用」します。

この時、運よく、引数の残骸が壊れずに残っていれば、あたかも、引数が正しく指定されたのと同じ動作をしてしまいます。

うまく動いてしまうのは「運よく、引数の残骸が壊れずに残っている時だけ」なので「同じ働きをしたのは偶然で、たまたま運が良かっただけ」です。

運が悪ければ、何が起こるか判りません。メモリアクセス例外が出たり、別の変数の中身を書き換えたり、プログラムが予期せぬ動作をしたりします。

投稿日時 - 2012-02-13 16:08:08

お礼

getsの引数をputsが勝手に使っているわけですね。
これを書いた人は意図してこんな動作させているようです。

投稿日時 - 2012-02-15 04:55:17

ANo.1

putsを宣言無しで呼び出していませんか?
具体的には、<stdio.h>をインクルードせず、自分で宣言を書くこともせずに呼び出していませんか?

そうだとすると、実引数の型も個数もまったくチェックされません。
コンパイルは通ったとしても未定義の動作になります。

投稿日時 - 2012-02-13 15:53:45

お礼

仰る通り、インクルードは書いておりません。
インクルードしないと引数の型をチェックしないというのは知っていたのですが、強行してくれるのは知りませんでした。

投稿日時 - 2012-02-15 04:40:59

あなたにオススメの質問