2013-03-21 6 views
15

私はhereを読んだことがあります。合格したメモリバッファが決して再び使用されないことが分かっていれば、コンパイラはmemsetへの呼び出しを自由に削除できます。そんなことがあるものか? memsetはちょうど普通の関数であり、コンパイラは内部で何が起こったとしても副作用がないと想定する権利はないと私には思われます(コア言語の観点から)。`memset`関数呼び出しをコンパイラで削除できますか?

linked articleには、Visual C++ 10がどのようにしてmemsetを削除したかが示されています。マイクロソフトのコンパイラは標準コンプライアンスをリードしていないことを知っています。標準に準拠しているのですか、それともmsvc-ismですか?それは標準に従ってだ場合は、手の込んだしてください;)

EDIT: @Cubbi

次のコード:行とMinGWの下コンパイル

void testIt(){ 
    char foo[1234]; 
    for (int i=0; i<1233; i++){ 
     foo[i] = rand()%('Z'-'A'+1)+'A'; 
    } 
    foo[1233]=0; 
    printf(foo); 
    memset(foo, 0, 1234); 
} 

g++ -c -O2 -frtti -fexceptions -mthreads -Wall -DUNICODE -o main.o main.cpp 
g++ -Wl,-s -Wl,-subsystem,console -mthreads -o main.exe main.o 
objdump -d -M intel -S main.exe > dump.asm 

は出力を与えました:

4013b0: 55      push ebp 
4013b1: 89 e5     mov ebp,esp 
4013b3: 57      push edi 
4013b4: 56      push esi 
4013b5: 53      push ebx 
4013b6: 81 ec fc 04 00 00  sub esp,0x4fc 
4013bc: 31 db     xor ebx,ebx 
4013be: 8d b5 16 fb ff ff  lea esi,[ebp-0x4ea] 
4013c4: bf 1a 00 00 00   mov edi,0x1a 
4013c9: 8d 76 00    lea esi,[esi+0x0] 
4013cc: e8 6f 02 00 00   call 0x401640 
4013d1: 99      cdq  
4013d2: f7 ff     idiv edi 
4013d4: 83 c2 41    add edx,0x41 
4013d7: 88 14 1e    mov BYTE PTR [esi+ebx*1],dl 
4013da: 43      inc ebx 
4013db: 81 fb d1 04 00 00  cmp ebx,0x4d1 
4013e1: 75 e9     jne 0x4013cc 
4013e3: c6 45 e7 00    mov BYTE PTR [ebp-0x19],0x0 
4013e7: 89 34 24    mov DWORD PTR [esp],esi 
4013ea: e8 59 02 00 00   call 0x401648 
4013ef: 81 c4 fc 04 00 00  add esp,0x4fc 
4013f5: 5b      pop ebx 
4013f6: 5e      pop esi 
4013f7: 5f      pop edi 
4013f8: c9      leave 
4013f9: c3      ret 

行4013eaにはmemset呼び出しがあるので、mingwはそれを削除していません。 mingwはWindowsスキンのGCCですから、GCCも同じことをやっていると思います。私はLinuxに再起動するときにチェックします。

このようなコンパイラを見つけるのにまだ問題がありますか?

EDIT2:私はちょうどGCCの__attribute__ ((pure))知った

。コンパイラがmemsetについて特別なことを知っているわけではなく、それをエリートするのではなく、ヘッダーにも許されています - それを使用するプログラマも見なければなりません;)私のmingwはmemset宣言にこの属性を持たないので、私が期待しているように、何に関係なくアセンブリ。私はこれを調べなければならないでしょう。

+8

しかし、 'memset()'は通常の関数ではありません。コンパイラは*副作用がないことを知っているので、しばしば特別な扱いを受ける。 – Mysticial

+0

このような場合にmemsetを排除しないコンパイラを見つけるのが難しいです。 – Cubbi

+0

@Mysticialそれは答えになるだろう。 –

答えて

11

"コンパイラは、内部で何が起こったとしても、副作用がないと想定する権利はありません。

これは間違いありません。しかし、コンパイラが実際にが実際にの内部で起こっていることを知っていて、実際ににはの副作用がないと判断できる場合、コンパイラは必要ありません。

これはほとんどすべてのコンパイラの最適化の仕組みです。コードに「X」と表示されます。コンパイラは、 "Y"が真であれば、コード "X"をコード "Z"に置き換えることができ、検出可能な差異はないと判断する。 "Y"が真であると判断し、 "X"を "Z"に置き換えます。例えば

void func() 
{ 
    int j = 2; 
    foo(); 
    if (j == 2) bar(); 
    else baz(); 
} 

コンパイラはfoo(); bar();にこれを最適化することができます。コンパイラはfoojの値を合法的に変更できないことを確認できます。 foo()が何とか魔法のようにjがスタック上にあるかどうかを調べて修正した場合、最適化はコードの動作を変更しますが、それはプログラマーの「魔法」の誤りです。

void func() 
{ 
    int j = 2; 
    foo(&j); 
    if (j == 2) bar(); 
    else baz(); 
} 

今ではfooが合法的にどんな魔法なしjの値を変更することができない可能性があるため。コンパイラがfooの内部を見ることができないと仮定すると、場合によってはそれが可能です。

"magic"を実行すると、コンパイラはコードを破壊する最適化を行うことができます。ルールに固執し、魔法を使わないでください。

リンク先の例では、コードはコンパイラが決してアクセスしない変数に特定の値を設定することを躊躇し、すぐに存在しなくなります。コンパイラは、コードの操作に影響を与えないものを実行する必要はありません。

コードに影響を及ぼす唯一の方法は、スタックの未割り当て部分を覗いたり、以前に持っていた値を持つスタック上の新しい割り当てに頼った場合です。コンパイラにそれを要求すると、ローカル変数をレジスタに置き換えるなど、膨大な数の最適化が不可能になります。

+0

jのすべてがローカルで処理され、memset-memsetのような関数に入れているあなたの例のコードには違いがあります。これは実際のコードであり、すでにコンパイルされたファイルに存在します(すべてのコンパイラにはヘッダがまとめられています)。それはインラインではないので、コンパイラはどこにでも任意の値を置いていると仮定する権利はありません。単に、どのパラメータを渡すべきかを知る必要があります。実際のmemset実装と私のコードを結合しているのはリンカです。 –

+0

関数宣言に適切な属性が与えられていると、コンパイラによってのみ省略できます。したがって、 'memset'は特別な扱いを受けません。私の2回目の編集を見てください。 –

+0

@j_kubikそれは必ずしも真実ではありません。この属性は、コンパイラが 'memset'への呼び出しを最適化するためには必要ありません。多くのコンパイラは、組み込み関数として 'memset'や' strlen'のような関数を実装しています(少なくとも、それらを組み込み関数として扱うオプションを有効にすることができます)。この場合、コンパイラはこれらの関数とその動作方法について特別な知識を持っているため、呼び出しに目に見える副作用がないと判断した場合、それらを "デッドコード"として排除することができます。 –