5

clangとを使用して次のコードをコンパイルし、gcc両方を次のように呼び出しました-O3

#include <stdio.h>
#include <stdlib.h>

static void a(int n) {
  if (n == 0) return;

  printf("descending; a=%i\n", n);
  a(n-1);
}

int main() {
  a(5);
  return 0;
}

生成される主な関数は次のgccとおりです(最後にNOPはありません)。

08048310 <main>:
 8048310:   55                      push   %ebp
 8048311:   89 e5                   mov    %esp,%ebp
 8048313:   83 e4 f0                and    $0xfffffff0,%esp
 8048316:   83 ec 10                sub    $0x10,%esp
 8048319:   c7 44 24 04 05 00 00    movl   $0x5,0x4(%esp)
 8048320:   00 
 8048321:   c7 04 24 14 85 04 08    movl   $0x8048514,(%esp)
 8048328:   e8 c7 ff ff ff          call   80482f4 <printf@plt>
 804832d:   c7 44 24 04 04 00 00    movl   $0x4,0x4(%esp)
 8048334:   00 
 8048335:   c7 04 24 14 85 04 08    movl   $0x8048514,(%esp)
 804833c:   e8 b3 ff ff ff          call   80482f4 <printf@plt>
 8048341:   c7 44 24 04 03 00 00    movl   $0x3,0x4(%esp)
 8048348:   00 
 8048349:   c7 04 24 14 85 04 08    movl   $0x8048514,(%esp)
 8048350:   e8 9f ff ff ff          call   80482f4 <printf@plt>
 8048355:   c7 44 24 04 02 00 00    movl   $0x2,0x4(%esp)
 804835c:   00 
 804835d:   c7 04 24 14 85 04 08    movl   $0x8048514,(%esp)
 8048364:   e8 8b ff ff ff          call   80482f4 <printf@plt>
 8048369:   c7 44 24 04 01 00 00    movl   $0x1,0x4(%esp)
 8048370:   00 
 8048371:   c7 04 24 14 85 04 08    movl   $0x8048514,(%esp)
 8048378:   e8 77 ff ff ff          call   80482f4 <printf@plt>
 804837d:   31 c0                   xor    %eax,%eax
 804837f:   c9                      leave  
 8048380:   c3                      ret    

そして、これがからのものですclang

080483d0 <main>:
 80483d0:   55                      push   %ebp
 80483d1:   89 e5                   mov    %esp,%ebp
 80483d3:   56                      push   %esi
 80483d4:   83 ec 0c                sub    $0xc,%esp
 80483d7:   be 05 00 00 00          mov    $0x5,%esi
 80483dc:   8d 74 26 00             lea    0x0(%esi,%eiz,1),%esi
 80483e0:   89 74 24 04             mov    %esi,0x4(%esp)
 80483e4:   c7 04 24 e0 84 04 08    movl   $0x80484e0,(%esp)
 80483eb:   e8 04 ff ff ff          call   80482f4 <printf@plt>
 80483f0:   4e                      dec    %esi
 80483f1:   75 ed                   jne    80483e0 <main+0x10>
 80483f3:   31 c0                   xor    %eax,%eax
 80483f5:   83 c4 0c                add    $0xc,%esp
 80483f8:   5e                      pop    %esi
 80483f9:   5d                      pop    %ebp
 80483fa:   c3                      ret    

私の質問は次のとおりです。両方がスタックに静的文字列のアドレスを何度も書き込むコードを生成する理由はありますか?たとえば、clang生成されるコードが代わりにこのように表示されないのはなぜですか?

080483d0 <main>:
 80483d0:   55                      push   %ebp
 80483d1:   89 e5                   mov    %esp,%ebp
 80483d3:   56                      push   %esi
 80483d4:   83 ec 0c                sub    $0xc,%esp
 80483d7:   be 05 00 00 00          mov    $0x5,%esi
 80483dc:   8d 74 26 00             lea    0x0(%esi,%eiz,1),%esi
 80483e0:   c7 04 24 e0 84 04 08    movl   $0x80484e0,(%esp)
 80483e7:   89 74 24 04             mov    %esi,0x4(%esp)
 80483eb:   e8 04 ff ff ff          call   80482f4 <printf@plt>
 80483f0:   4e                      dec    %esi
 80483f1:   xx xx                   jne    80483e7 <main+0x17>
 80483f3:   31 c0                   xor    %eax,%eax
 80483f5:   83 c4 0c                add    $0xc,%esp
 80483f8:   5e                      pop    %esi
 80483f9:   5d                      pop    %ebp
 80483fa:   c3                      ret    
4

3 に答える 3

2

主な理由は、ABIが「スタックフレーム」の所有権をどのように定義するかです。簡単に言うと、関数を呼び出すとすぐに、その関数は現在のスタックフレーム(パラメーター、差出人住所、ローカル変数を含む)の所有権を取得します。

それで何でもできます。printf()スタックフレーム内のすべてのものを無傷に保つ保証はありません。そうするのは悪い習慣ですが、関数はこれを行うことができ、スタックを混乱させるコードを作成しました(たとえば、Cで記述されたCインタープリターで、コンパイルされた関数を呼び出すことができますprintf())。

したがって、戻り時に静的文字列へのポインタがスタック上にあるという保証はありませんprintf()printf()のカスタム実装を使用して別のCランタイムに対してリンクできるため、コンパイラはそれが動作することを想定できないことに注意してくださいprintf()

GCCがソースコードを見ることができる関数を呼び出してみてください。これらの場合、スタックをさらに最適化する可能性があります。

[編集]多分別の例がこれを理解しやすくします。場合によっては、GCCは関数の引数の受け渡しを最適化します。それらをスタックにプッシュする代わりに、CPUレジスタに渡されます。これに使用すると仮定しましょう%eax。引数が呼び出し元の関数によって所有されている場合、呼び出された関数は変更できません%eax

于 2012-08-15T13:59:41.823 に答える
2

Mac OS Xアプリケーションバイナリインターフェイスでは、スタックに渡されるパラメータは、呼び出されたルーチンによって変更される場合があります。したがって、呼び出し元のルーチンがフォーマット文字列のアドレスをスタックに置く場合、呼び出されたルーチンはスタック上のその場所に書き込むことができます。(通常、スタックの上位[以前]の場所に書き込むことは許可されていません。)したがって、呼び出し元のルーチンは、呼び出されたルーチンが戻った後、呼び出し元のルーチンがスタックに書き込んだパラメーターが変更されていないことを知ることができません。

これは、計算を実行するために引数を使用しているときに引数を変更する可能性のあるルーチンにとって便利です。たとえば、次のようなコードを使用できます。

int foo(int x)
{
    x *= x;
    return x+3;
}

明らかに、この単純なコードの場合、コンパイラーは、戻り値の計算を完了するために、実際に製品をxに格納する必要はありません。ただし、より複雑なルーチンを使用すると、コンパイラが値をxに格納することを決定する可能性がある場所を確認できます。

ABIの設計上の問題として、呼び出し元が変更されていない値に依存できるように、呼び出されたルーチンがこのスペースを使用することを禁止する方がよいかどうか疑問に思うかもしれません。ただし、この方法でパラメータを繰り返し使用することはあまり一般的ではなく、新しいコピーを作成するコストはごくわずかです。

また、スタックに書き込まれたフォーマット文字列のアドレスのみを変更できることに注意してください。Cセマンティクスでは、const charであるため、呼び出されたルーチンによってフォーマット文字列の内容が変更されないままである必要があります。

于 2012-08-15T14:05:15.807 に答える
-1

短い問題は関数呼び出しです。Cコンパイラは、関数呼び出しで何が起こるかについて実際に保証することはできません(Cの組み込みの1つである場合を除きますstrlen)。したがって、それらの周りを最適化する必要はありません。検討:

pthread_mutex_lock(&mutex);
val = 5;
pthread_mutex_unlock(&mutex);

コンパイラがvalへの書き込みがへの呼び出しとは無関係であると推測した場合pthread_mutex_lock、ロックの前にそれをプッシュする可能性があり、大規模な混乱が発生します。

コンパイラーは、関数呼び出しが行われる前の最後の書き込みを想定する必要があります。書き込みが何が起こっているかを正確に知っているコードのブロック内で発生する場合(つまり、関数呼び出しがないか、組み込みのみ)、必要に応じて最適化し、不要な書き込みを引き出すことができます。

関数が変数にアクセスできるかどうかをコンパイラーが推測する方法はありません(少なくとも停止性問題を解決しない限り)。別の関数は、そのシンボルがエクスポートされていない場合でも、以前にアドレスを設定したり、dlsym何かのアドレスを独自のバイナリで把握したりする場合があります。

于 2012-08-15T14:03:59.300 に答える