1. ホーム
  2. c

[解決済み] スタンフォード大学のチュートリアルとGCCとの衝突

2023-06-09 11:52:21

質問

によると この の動画(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 , この実験はうまくいくはずです . この値はスタックから "erased" されていませんし、初期化されていないので B() で初期化されていないので、スタックフレームが同じであれば、同じ値がまだそこにあるはずです。

私は、あえて推測すると int a 使用される の内部で void B() を使用した場合、コンパイラはそのコードを最適化し、スタックのその場所には5が書き込まれることはありませんでした。 試しに printfB() も同様に、うまくいくかもしれません。

また、コンパイラのフラグ、すなわち最適化レベルは、おそらくこの実験に影響を与えるでしょう。最適化を無効にするために -O0 を gcc に渡して最適化を無効にしてみてください。

編集してください。 あなたのコードをコンパイルしたところ gcc -O0 (64-bit)でコンパイルしたところ、確かにコールスタックに詳しい人が予想するように、プログラムは5を表示しました。 実際、それは -O0 . 32 ビットのビルドでは異なる動作になるかもしれません。

免責事項: 絶対にしないでください。 を使用しないでください。 のようなものを実際のコードで使用しないでください。

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] . つまり、この場合、ローカル変数 が同じ場所で終わってしまうのです! そこで printf() の呼び出しを A を呼び出すと、そのスタックフレームは AB を同一とし、プリント 55 .