質問
下記のプログラムのmain関数内の
int buf[1000];
buf[999] = 10;
の所がなにをしているのかわかりません。int buf[1000];buf[999] = 10;を削除してプログラムを実行した結果hello関数の呼び出した数が変わりました。hello関数の呼び出しを制限するためにあるのでは?と考えましたが、考えが正しいのか、どのような役割を果たしているのか解説していただけると嬉しいです。
該当のソースコード
#include <stdio.h> void hello(void) { fprintf(stderr, "hello!\n"); } void func(void) { void *buf[10]; static int i; for (i = 0; i < 100; i++) { buf[i] = hello; } } int main(void) { int buf[1000]; buf[999] = 10; func(); return 0; }
補足情報(FW/ツールのバージョンなど)
ここにより詳細な情報を記載してください。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答5件
0
目的はわからないという結論のようですが、私には想像がつきます。
buf[999] = 10; が手掛かりです。ここに、int buf[1000] 配列を間違いなく確保したいという気持ちが表れています。
作者はこういう事を考えたのです(もちろん私の想像)。
-
(タイトル通り)バッファオーバーランでリターンアドレスを上書きする実験をしよう。
-
func()関数は、スタック上の配列 char *buff[10]; に対して、
hello() 関数のエントリアドレスを(100個も)書いていけば、どこかはともかく、どこかでリターンアドレスを上書きするだろう(不正は百も承知)。その状態で func() がリターンすれば、本来の戻り先である main() の中ではなく、hello() 関数へ「リターン」していくだろう(実際、質問者の手元でも、私の手元でもそうなった)。
-
(何もしないまま、とはbuf[1000]を配置せずに)要素数10の配列に対して範囲を超えて100個も書けば予想外の不具合を起こすかもしれない。それで肝心の実験が失敗するようでは困る。
-
安全に(笑)バッファオーバーランしたいので、main() のスタック領域に
要素数1000の配列をとっておこう。こうすれば、上書きされるのは配列の先頭部分(インデックスの若い領域)とリターンアドレス付近だけで済むのではないか。
-
まてよ、int buf[1000]; だけだと、実際にはこの配列を使わないと見做され、コンパイラが最適化してしまい配列が割り当てられないかもしれない。
-
そうだ!buf[999] をアクセスすれば buf[0] ~ buf[999] を使うという意思をコンパイラに伝えられて配列を確実にスタックに配置できる・・・
最後の「buf[999] をアクセスすれば…」はC言語ではない、別の言語の経験が反映しているという感じもします。
プログラム中に現れる10, 100, 1000 という値は、この値でなければならないという根拠のある数字ではありません。だいたいこういう値にしておけば実験できるだろうという値です。
int buf[1000];buf[999] = 10;を削除してプログラムを実行した結果hello関数の呼び出した数が変わりました
もとより不正で未定義な動作を意図的に実行しているのですから、動作結果が違うことに不思議は無いのですが、
私が2つのCコンパイラで試したhello()の実行回数を次に示します。
int buf[1000]有 | int buf[1000]無 | |
---|---|---|
コンパイラA | 89回 | 89回 |
コンパイラB | 89回 | 789回 |
89回(100-10=90に近い回数)hello()を実行する理由は比較的容易に理解できますが、789回も実行する理由は私にもわかりません。これは上記の「予想外の不具合を起こすかも」に相当すると思います。大雑把な言い方ですが、コンパイラBの場合はbuf[1000]が無いとメモリをハデに壊してしまうのでしょう。元の作者も似たような現象に遭遇したのではないでしょうか。
つまり、int buf[1000]があることで上記「安全に(笑)バッファオーバーランしたい」を実現できる場合があると言えます。
投稿2020/05/09 08:52
総合スコア1382
0
なにをしているのかわかりません。
誰にもわかりません。バッファオーバーランは未定義の動作ですので、「buf[999] = 10;
と書いたからこのような動作が起きる」ということは、C言語のソースコードレベルでは何も言えません。
実際に生成した機械語と突き合わせて、「結果的にどういう意味を持つのか」は分析できるかもしれません。
投稿2020/05/08 03:16
総合スコア146018
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
ベストアンサー
これって、この段階で(配列の外部に書き込みをしているため)未定義動作です。
c
1 for (i = 0; i < 100; i++) { 2 buf[i] = hello; 3 }
また、clang 10.0で最適化をかけるとmain()は、func()が未定義動作のため、
何もしないで0を返すだけの関数に成ります。
asm
1main: # @main 2 .cfi_startproc 3# %bb.0: 4 xorl %eax, %eax 5 retq 6.Lfunc_end2: 7 .size main, .Lfunc_end2-main 8 .cfi_endproc
投稿2020/05/08 05:45
編集2020/05/08 06:03総合スコア6851
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/05/08 05:53
2020/05/08 06:06
2020/05/08 06:22
2020/05/08 06:29
2020/05/08 07:04
2020/05/08 07:28
2020/05/08 07:33 編集
2020/05/08 07:34
2020/05/08 08:20
0
質問のコードは、C言語レベルでは未定義の動作であり、
OS やコンパイラによって実行結果に違いがあります。
VC++ や Ubuntu の gcc では、hello! は表示されませんでした。しかし、
Windows 10 上の cygwin の gcc では、80数個の hello! が表示されました。
そこで次のようなコードに書き換えて検証してみました。
C
1#include <stdio.h> 2 3void **vp; 4 5void show(void) 6{ 7 for (int i = 0; i < 100; i++) 8 fprintf(stderr, "%p: buf[%d] = %p\n", vp + i, i, vp[i]); 9} 10 11void hello(void) 12{ 13 fprintf(stderr, "hello!\n"); 14} 15 16void func(void) 17{ 18 void *buf[10] = { 0 }; 19 static int i; 20 vp = buf; 21 show(); 22 23 for (i = 0; i < 100; i++) { 24 buf[i] = hello; 25 } 26} 27 28int main(void) 29{ 30 fprintf(stderr, "hello = %p\n", hello); 31 fprintf(stderr, "func = %p\n", func); 32 fprintf(stderr, "main = %p\n", main); 33 int buf[1000]; 34 static int i; 35 for (i = 0; i < 999; i++) buf[i] = i; 36 buf[999] = 10; 37 fprintf(stderr, "main: &buf[0] = %p\n", &buf[0]); 38 fprintf(stderr, "main: &buf[999] = %p\n", &buf[999]); 39 40 func(); 41 42 return 0; 43}
実行結果
hello = 0x1004010ff func = 0x100401131 main = 0x1004011de main: &buf[0] = 0xffffbc80 main: &buf[999] = 0xffffcc1c 0xffffbc00: buf[0] = 0x0 0xffffbc08: buf[1] = 0x0 0xffffbc10: buf[2] = 0x0 0xffffbc18: buf[3] = 0x0 0xffffbc20: buf[4] = 0x0 0xffffbc28: buf[5] = 0x0 0xffffbc30: buf[6] = 0x0 0xffffbc38: buf[7] = 0x0 0xffffbc40: buf[8] = 0x0 0xffffbc48: buf[9] = 0x0 0xffffbc50: buf[10] = 0xffffbce0 0xffffbc58: buf[11] = 0x1004012e4 0xffffbc60: buf[12] = 0x0 0xffffbc68: buf[13] = 0x0 0xffffbc70: buf[14] = 0xffffcc1c 0xffffbc78: buf[15] = 0x18015f160 0xffffbc80: buf[16] = 0x100000000 0xffffbc88: buf[17] = 0x300000002 0xffffbc90: buf[18] = 0x500000004 ... 0xffffbf10: buf[98] = 0xa5000000a4 0xffffbf18: buf[99] = 0xa7000000a6 hello! hello! hello! ... hello!
これから分かることは、
func の buf[16] が main の {buf[1],buf[0]}
func の buf[17] が main の {buf[3],buf[2]}
func の buf[99] が main の {buf[167],buf[166]}
ポインタは 8バイトで、int は 4バイトなので、このようになります。
実行ファイルを逆アセンブルすると、
buf[10] が main のフレームポインタ、
buf[11] が main で func を呼び出したときの戻り番地だと分かります。
buf[12]~buf[15] はスタックフレームのパディングだと思います。
func で 10個しかない buf に 100個の hello を書き込むと
スタック上にある戻り番地や main の buf[167] までをすべて壊してしまいます。
func の実行が終了して、main に戻ろうとするとき、戻り番地が hello に
変わっているので、そこへジャンプします。
そして、"hello!\n" を表示します。
hello の実行が終了して、呼び出し元に戻ろうとするとき、スタックにあるのは
hello のアドレスです。それを pop してプログラムカウンタに入れるので
また hello にジャンプします。
こうして、90回近い hello の実行をして、最後はどこか分からないところへ
飛んでいき実行が終了するようです。
投稿2020/05/09 05:28
総合スコア8224
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/05/11 04:49
2020/05/11 14:25
2020/05/12 09:28