this movie(約38分)によると、同じローカル変数を持つ2つの関数がある場合、それらは同じスペースを使用します。したがって、次のプログラムは5
。 gcc
結果でコンパイル-1218960859
。どうして?
プログラム:
#include <stdio.h>
void A()
{
int a;
printf("%i",a);
}
void B()
{
int a;
a = 5;
}
int main()
{
B();
A();
return 0;
}
要求どおり、逆アセンブラからの出力は次のとおりです。
0804840c <A>:
804840c: 55 Push ebp
804840d: 89 e5 mov ebp,esp
804840f: 83 ec 28 sub esp,0x28
8048412: 8b 45 f4 mov eax,DWORD PTR [ebp-0xc]
8048415: 89 44 24 04 mov DWORD PTR [esp+0x4],eax
8048419: c7 04 24 e8 84 04 08 mov DWORD PTR [esp],0x80484e8
8048420: e8 cb fe ff ff call 80482f0 <printf@plt>
8048425: c9 leave
8048426: c3 ret
08048427 <B>:
8048427: 55 Push ebp
8048428: 89 e5 mov ebp,esp
804842a: 83 ec 10 sub esp,0x10
804842d: c7 45 fc 05 00 00 00 mov DWORD PTR [ebp-0x4],0x5
8048434: c9 leave
8048435: c3 ret
08048436 <main>:
8048436: 55 Push ebp
8048437: 89 e5 mov ebp,esp
8048439: 83 e4 f0 and esp,0xfffffff0
804843c: e8 e6 ff ff ff call 8048427 <B>
8048441: e8 c6 ff ff ff call 804840c <A>
8048446: b8 00 00 00 00 mov eax,0x0
804844b: c9 leave
804844c: c3 ret
804844d: 66 90 xchg ax,ax
804844f: 90 nop
はい、はい、これは未定義の動作です、なぜなら初期化されていない変数を使用しているからです1。
ただし、x86アーキテクチャでは2、この実験はうまくいくはずです。値はスタックから「消去」されず、B()
で初期化されないため、スタックフレームが同一であれば、同じ値がまだ存在しているはずです。
_int a
_がvoid B()
内でusedされていないため、コンパイラがそのコードを最適化し、5がスタック上のその場所に書き込まれることはありません。 B()
にもprintf
を追加してみてください-うまくいくかもしれません。
また、コンパイラフラグ(最適化レベル)もこの実験に影響を与える可能性があります。 _-O0
_をgccに渡して、最適化を無効にしてみてください。
編集:_gcc -O0
_(64ビット)を使用してコードをコンパイルしたところ、実際に、プログラムは5を出力します。実際、_-O0
_がなくても機能しました。 32ビットのビルドは異なる動作をする場合があります。
免責事項:絶対にしないでくださいever「実際の」コードではこのようなものを使用してください!
1-これが正式に「UB」であるか、または単に予測できないかどうかについて、議論が続いています 下 。
2-また、x64、およびコールスタック(少なくともMMUを備えたもの)を使用する他のすべてのアーキテクチャ
それが機能しなかった理由を見てみましょう。これは32ビットで最もよく見られるので、_-m32
_でコンパイルします。
_$ gcc --version
gcc (GCC) 4.7.2 20120921 (Red Hat 4.7.2-2)
_
_$ gcc -m32 -O0 test.c
_(最適化は無効)でコンパイルしました。これを実行すると、ゴミが表示されます。
_$ objdump -Mintel -d ./a.out
_を見る:
_080483ec <A>:
80483ec: 55 Push ebp
80483ed: 89 e5 mov ebp,esp
80483ef: 83 ec 28 sub esp,0x28
80483f2: 8b 45 f4 mov eax,DWORD PTR [ebp-0xc]
80483f5: 89 44 24 04 mov DWORD PTR [esp+0x4],eax
80483f9: c7 04 24 c4 84 04 08 mov DWORD PTR [esp],0x80484c4
8048400: e8 cb fe ff ff call 80482d0 <printf@plt>
8048405: c9 leave
8048406: c3 ret
08048407 <B>:
8048407: 55 Push ebp
8048408: 89 e5 mov ebp,esp
804840a: 83 ec 10 sub esp,0x10
804840d: c7 45 fc 05 00 00 00 mov DWORD PTR [ebp-0x4],0x5
8048414: c9 leave
8048415: c3 ret
_
B
では、コンパイラが0x10バイトのスタックスペースを予約し、_int a
_変数を_[ebp-0x4]
_で5に初期化したことがわかります。
ただし、A
では、コンパイラは_int a
_を_[ebp-0xc]
_に配置しました。したがって、この場合、ローカル変数は同じ場所にありません。 A
にもprintf()
呼び出しを追加すると、A
とB
のスタックフレームが同じになり、_55
_が出力されます。 。
未定義の動作です。初期化されていないローカル変数には不確定な値があり、それを使用すると未定義の動作が発生します。
覚えておくべき重要なことの1つ-しないでくださいこれまでにそのようなものに依存し、neverこれを実際のコードで使用してください!これは興味深い機能であり(常に正しいとは限りません)、機能やそのようなものではありません。そのような「機能」によって生成されたバグを見つけようとしている自分を想像してみてください-悪夢。
ところで-CとC++は、そのような「機能」でいっぱいです。ここに[〜#〜]素晴らしい[〜#〜]スライドショーがあります:- http://www.slideshare.net/olvemaudal/deep-c したがって、より類似した「機能」を表示したい場合は、内部の内容とその機能を理解するだけで、このスライドショーを見るだけで、すぐに表示されます。後悔しているし、経験豊富なc/c ++プログラマーのほとんどでさえ、これから多くを学ぶことができると確信している。
関数A
では、変数a
は初期化されておらず、その値を出力すると未定義の動作が発生します。
一部のコンパイラでは、a
内の変数A
とa
内のB
が同じアドレスにあるため、5
、しかし再び、未定義の動作に依存することはできません。
_gcc -Wall filename.c
_を使用してコードをコンパイルします。これらの警告が表示されます。
_In function 'B':
11:9: warning: variable 'a' set but not used [-Wunused-but-set-variable]
In function 'A':
6:11: warning: 'a' is used uninitialized in this function [-Wuninitialized]
_
初期化されていない変数をcに出力すると、未定義の動作が発生します。
セクション6.7.8 C99標準の初期化は言う
自動保存期間を持つオブジェクトが明示的に初期化されていない場合、その値は不確定です。静的ストレージ期間を持つオブジェクトが明示的に初期化されていない場合は、次のようになります。
_— if it has pointer type, it is initialized to a null pointer;
— if it has arithmetic type, it is initialized to (positive or unsigned) zero;
— if it is an aggregate, every member is initialized (recursively) according to these rules;
— if it is a union, the first named member is initialized (recursively) according to these rules.
_
Edit1
As @Jonathon Reinhart _-O
_フラグ_gcc-O0
_を使用して最適化を無効にすると、出力5が得られる可能性があります。
しかし、これはまったく良い考えではありません。これを運用コードで使用しないでください。
_-Wuninitialized
_これは重要な警告の1つですこれを検討する必要がありますデーモンの実行中にクラッシュを引き起こすなど、本番環境に大きなダメージを与えるこの警告を無効にしたりスキップしたりしないでください。
Edit2
ディープC スライドの説明なぜ結果は5 /ガベージなのか。これらのスライドからこの情報を少し修正して追加し、この回答の効果を少し高めます。
ケース1:最適化なし
_$ gcc -O0 file.c && ./a.out
5
_
おそらく、このコンパイラには、再利用する名前付き変数のプールがあります。たとえば、変数aがB()
で使用および解放された場合、A()
が整数名a
を必要とする場合、変数は同じメモリ位置を取得します。 B()
の変数の名前をb
に変更すると、_5
_が返されるとは思いません。
ケース2:最適化あり
オプティマイザーが起動すると、多くのことが発生する可能性があります。この場合、副作用がないため、B()
の呼び出しはスキップできると思います。また、A()
がmain()
にインライン化されている場合、つまり、関数呼び出しがない場合でも、驚かないでしょう。 (ただし、A ()
にはリンカーの可視性があるため、別のオブジェクトファイルが関数とリンクする場合に備えて、関数のオブジェクトコードを作成する必要があります)。とにかく、コードを最適化すると、出力される値が別の値になると思います。
_gcc -O file.c && ./a.out
1606415608
_
ごみ!