プロが教える店舗&オフィスのセキュリティ対策術

#include<stdio.h>

main()
{
printf( "%4d" ) ;

}


で実行してみると、実行するたびに結果の値が変化しました。

これはどういうことが起こっているのでしょうか?解説お願いします。

A 回答 (7件)

普通、Cの関数は引数の数があらかじめ決まっています。


ところが、printf() は、prinff("%d %d %d", a, b, c); などといくられも引数を渡すことができます(この例なら4個)

で、printf("%4d"); だと、printf() は最初の引数 "%4d" を見た時点で、「この後引数が1個ある」という前提で動きます。
ここからは、説明のために、「よく使われる」処理です。(必ずしもこういう処理をしているわけではありません)

正しい printf("%d %d", a, b); なら、printf() の呼び出し側は、引数を(スタックというところに)逆順に積み上げます。

printf() が呼び出された時点で、

"%d %d"
b
a

と左側が上に来るように積み上げられています。
ここで、printf() は、最初の引数("%d %d")を読みます。
※printf() は可変引数で、いくらでも引数を渡せますが、最初の引数だけは、 char * と型が決まっています。だから、最初の引数は正しく読めることになっています。

"%d %d" という引数を評価した時点で、printf() は、(%d が2つあるから)int の引数2個が、この後に積んであると解釈します。
そして、順次、引数として渡されたデータを取り出すと、正しく、b と a が取得できるわけです。

では、printf("%4d") だけならどうなるか?

同じように、printf() は、"%4d" を評価した時点で、int型の引数が1個あると解釈します。
そして、その int 型の引数として渡されたデータを取得しようとします。

が、当然そんなものはありません。

無いとどうなるかといえば、スタックと言っても、メモリ領域です。
"%4d" が積んであったその下に当たるアドレスにもメモリが存在します(ないこともあります)
メモリが存在すれば、当然、何かデータが書いてあります。(ごみかもしれません)
printf() は、そのごみデータを、「次の引数として渡されたデータ」だと解釈して、そのまま表示します。

なので、もともと自分で設定したデータでは無いものを読みに行って、「それが表示すべきデータだ」と信じて表示するわけです。
    • good
    • 1
この回答へのお礼

納得がいきました!ありがとうございましたm(__)m。

お礼日時:2011/10/20 15:43

例えば、



int a;

とだけ書いた場合、コンパイラはa用のメモリを確保してaという変数で使用できるようにするが、そのメモリにどんな値が入っているかは感知しない。直前にそのメモリを使用していた状況による。

同様に、可変引数の関数が呼ばれた場合は、コンパイラは手順通りに引数(であるはず)のデータを取り出すだけで、引数が不足した場合にどんなデータが取り出されるかは感知しない。

言語仕様として未定義とされていることに対して、解説しろと言われても、できるわけがないし、仮に解説したとしても何の根拠もない。

特定のコンパイラがどう動作するかはわかっても、所詮「そのコンパイラでは」というレベルのこと。

「値を表示しろという命令なのに、値がなかったんで、適当に持ってきました」くらいに考えるべき。
    • good
    • 0

>ど素人なもので・・・・わかりません・・・・・


>
>"%4d" がレジスタのラベルになって、そのレジスタの中身がプロセスを作成する度に変化していくということですか?

私もコンパイラなどが専門ではないので結構間違っているかもしれませんが。。。
レジスタっていうのは、CPU自身が持ってる、メモリよりも速い記憶装置です。
プログラムカウンタ(いま、メモリ上のどこを実行しているか?)などを記憶していたり、計算途中の結果を保存してたりします。

"%4d"はレジスタのラベルではなく、printfが内部で解析し値と置き換える処理を行うためのフォーマットです。
その際にprintfは引数を参照する必要があるのですが、そこでレジスタが参照されるのではないでしょうか?

そのレジスタの中身がプロセスを作るたびに変化してる(たぶん、前に他のプロセスがレジスタを使った値が残ってる?)ので違った値が出るのではないでしょうか?

間違ってたらすいません・・;;
    • good
    • 0

>"%4d\n"の文字列定数が置かれたアドレス  だとして、それは常時変化し続けるものなのですか?


「常時変化」って発想はちょっとまずい。実行されるたびに異なるっていうことですよ。毎回同じアドレスが空いているとは限らないのは分かると思います。
    • good
    • 0

環境依存かな?と思ったので、


MacOSX SnowLeopardでは確認できませんでしたが、Fedora14で確認しました。
両方共gccを使用しました。

とりあえず、アセンブリを吐いて比較してみます。
上記のコードをhogehoge.cとして

printf( "%4d",10);

引数を正しく指定したコードをhogehoge2.cとします。
アセンブリを吐き出すには

gcc -S hogehoge.c
gcc -S hogehoge2.c

を実行しました、コードは以下です。

.file"hogehoge.c"
.section.rodata
.LC0:
.string"%4d"
.text
.globl main
.typemain, @function
main:
.LFB0:
.cfi_startproc
pushq%rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq%rsp, %rbp
.cfi_def_cfa_register 6
movl$.LC0, %eax
movq%rax, %rdi
movl$0, %eax
callprintf
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.sizemain, .-main
.ident"GCC: (GNU) 4.5.1 20100924 (Red Hat 4.5.1-4)"
.section.note.GNU-stack,"",@progbits

これと、hogehoge2.cのアセンブリをdiffとります。

1c1
< .file"hogehoge2.c"
---
> .file"hogehoge.c"
17d16
< movl$10, %esi

関数を呼ぶときに、引数を渡す必要がありますが、それらはレジスタなどに書きこまれます。
プロセスが作成されるたびに、レジスタは初期化されるのではなく、前に使った値が残っています。
hogehoge.cの場合、レジスタに値が代入されないため、そのまま古いレジスタの値が使われたのだと思います。
    • good
    • 0
この回答へのお礼

ど素人なもので・・・・わかりません・・・・・

"%4d" がレジスタのラベルになって、そのレジスタの中身がプロセスを作成する度に変化していくということですか?

お礼日時:2011/10/19 11:38

http://okwave.jp/qa/q7079070.html
これのBA。
つーか第2引数を省略するなんてことどこの誰が教えたんだか…
    • good
    • 0
この回答へのお礼

("%4d\n") , i*j ;  は  ("%4d\n", i*j);  のはずなのに値が出ていたのが不思議だったのでやってみました。


"%4d\n"の文字列定数が置かれたアドレス  だとして、それは常時変化し続けるものなのですか?

お礼日時:2011/10/19 11:33

printf の第1引数、第2引数がキチンと定義されていないので、何の値を出力しているのかも不明な状態ですね。



main()
{
 int a=10;

printf( "%4d",a ) ;

}

など、引数を2つ定義するのでは・・・?
    • good
    • 0

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