2012-03-23 15 views
7

を作るん対理由:++ 2010コンパイラcは、私が誤ってそれを転写しかしだから、最近、私はstrcpyを、彼らは</p> <pre><code>while (*dst++ = *src++) ; </code></pre> <p>ような実装を示して戻っKに& Rを考えていた同様の機能のための別のアセンブリコード

いずれにしても、コンパイラがこれらの2つのコードを実際に生成するかどうかは私には分かります。 srcとdstはインクリメントされていますが、決して使用されていないので、コンパイラは生成されたマシンコード内でそれらを変数として永続的に保存しようとしないと知っていると思いました。

32ビットリリースモード(/ O2)のVS 2010 C++ SP1ビルドでwindows7を使用すると、上記の両方のインカネーションのディスアセンブルコードが取得されました。関数自体が入力を直接参照したり、インライン展開されたりするのを防ぐため、関数ごとにdllを作成しました。私はASMのプロローグとエピローグを省略しました。

while (*dst++ = *src++) 
6EBB1003 8B 55 08    mov   edx,dword ptr [src]  
6EBB1006 8B 45 0C    mov   eax,dword ptr [dst]  
6EBB1009 2B D0    sub   edx,eax    //prepare edx so that edx + eax always points to src  
6EBB100B EB 03    jmp   docopy+10h (6EBB1010h) 
6EBB100D 8D 49 00    lea   ecx,[ecx]    //looks like align padding, never hit this line 
6EBB1010 8A 0C 02    mov   cl,byte ptr [edx+eax] //ptr [edx+ eax] points to char in src :loop begin 
6EBB1013 88 08    mov   byte ptr [eax],cl  //copy char to dst 
6EBB1015 40     inc   eax     //inc src ptr 
6EBB1016 84 C9    test  cl,cl     // check for 0 (null terminator) 
6EBB1018 75 F6    jne   docopy+10h (6EBB1010h) //if not goto :loop begin 
     ; 

上記のコードには、基本的に1つのループがあり、nullと1つのメモリコピーのみがチェックされます。

今すぐ私のミスのバージョンを見てみましょう:私のバージョンで

while (*dst = *src) 
6EBB1003 8B 55 08    mov   edx,dword ptr [src] 
6EBB1006 8A 0A    mov   cl,byte ptr [edx] 
6EBB1008 8B 45 0C    mov   eax,dword ptr [dst] 
6EBB100B 88 08    mov   byte ptr [eax],cl  //copy 0th char to dst 
6EBB100D 84 C9    test  cl,cl     //check for 0 
6EBB100F 74 0D    je   docopy+1Eh (6EBB101Eh) // return if we encounter null terminator 
6EBB1011 2B D0    sub   edx,eax 
6EBB1013 8A 4C 02 01   mov   cl,byte ptr [edx+eax+1] //get +1th char :loop begin 
    { 
     src++; 
     dst++; 
6EBB1017 40     inc   eax     
6EBB1018 88 08    mov   byte ptr [eax],cl  //copy above char to dst 
6EBB101A 84 C9    test  cl,cl     //check for 0 
6EBB101C 75 F5    jne   docopy+13h (6EBB1013h) // if not goto :loop begin 
    } 

、私はそれ最初にコピー先への0番目のcharことがわかり、その後、ヌルをチェックした後、最終的にはそれがチェックするループに入りますもう一度nullになります。したがって、ループはほとんど同じままですが、ループの前の0番目の文字を処理します。これは、もちろん、最初の場合と比較して準最適になるでしょう。

コンパイラが最初の例と同じ(またはほぼ同じ)コードを作成できない理由が分かっているのでしょうか。これはmsコンパイラ特有の問題か、おそらく私のコンパイラ/リンカ設定ですか?


ここでは完全なコードですが、2つのファイル(1つの機能がもう一方の機能を置き換えます)。

// in first dll project 
__declspec(dllexport) void docopy(const char* src, char* dst) 
{ 
    while (*dst++ = *src++); 
} 

__declspec(dllexport) void docopy(const char* src, char* dst) 
{ 
    while (*dst = *src) 
    { 
     ++src; 
     ++dst; 
    } 
} 


//seprate main.cpp file calls docopy 
void docopy(const char* src, char* dst); 
char* source ="source"; 
char destination[100]; 
int main() 
{ 

    docopy(source, destination); 
} 
+0

あなたが始めたCコード全体を投稿してください。 srcとdstの宣言のために異なるかもしれませんが、私は知ることができません。削除したアセンブラプリアンブルは同じですか?それがあれば貼り付ける必要はありません。 – gbulmer

+0

どちらの場合も、エピローグとプロローグを除いて、コード全体です。 – skimon

+0

これは、多くの読者が式内の「=」が「==」の入力ミスとして認識するため、コーディングのコーディングスタイルが悪いことです。 –

答えて

10

最初の例では、srcがヌル文字を指していても、ポストインクリメントが常に発生するためです。同じ開始状況では、2番目の例ではポインタがインクリメントされません。

+0

ああ、私はあなたが意味することを見て、あなたの答えを受け入れる気がします。コンパイラーはdstとsrcをインクリメントしても、whileとの比較やコピーのコンテキスト以外では使用されず、基本的に最初の例と同じロジックになりますので、ここで違いはありませんか? – skimon

+0

理論的には、 'src'と' dst'がローカルであり、ループの後で再び使われなければ、オプティマイザは前提を作ることができます。おそらくより積極的なオプティマイザがそうするでしょう。一方、オプティマイザには比較対象となる 'その他の'バージョンはありません。 2番目の情報源が与えられているので、それは効率のために「余分な」増加を加えることができるということを示唆するものは何もありません。 – AShelly

+0

私は元のメッセージに完全なコードを掲載しました。 @AShelly私はあなたが正しいと思う、おそらく、コンパイラはここで何らかの理由で慎重すぎる。 – skimon

2

もちろん、コンパイラには他のオプションがあります。 gcc-4.5.1が-O1で生成するものは、 "最初のバイトをコピーしてから0にならない場合はループに入ります"です。 -O2と-O3と、それはK & Rループのために生成するものと非常に類似している

.LFB0: 
    .cfi_startproc 
    jmp  .L6    // jump to copy 
    .p2align 4,,10 
    .p2align 3 
.L4: 
    addq $1, %rdi  // increment pointers 
    addq $1, %rsi 
.L6:      // copy 
    movzbl (%rdi), %eax // get source byte 
    testb %al, %al  // check for 0 
    movb %al, (%rsi)  // move to dest 
    jne  .L4    // loop if nonzero 
    rep 
    ret 
    .cfi_endproc 

を生成します。それが実際に優れているかどうかは私が言うことはできませんが、それは良い見えます。

別にループにジャンプから、K & Rループの命令はちょうど異なっ命じ、まったく同じです:

.LFB0: 
    .cfi_startproc 
    .p2align 4,,10 
    .p2align 3 
.L2: 
    movzbl (%rdi), %eax // get source byte 
    addq $1, %rdi  // increment source pointer 
    movb %al, (%rsi)  // move byte to dest 
    addq $1, %rsi  // increment dest pointer 
    testb %al, %al  // check for 0 
    jne  .L2    // loop if nonzero 
    rep 
    ret 
    .cfi_endproc 
+0

興味深いことに、gcc-4.4.4で64ビットでコンパイルすると、私が説明したのと同じ動作をします。しかし、32ビット(-m32 -O3)では、 "最初のバイトをコピーしてから0にならない場合はループに入ります"という結果が出ます。おそらく64ビットでより多くのレジスタを使用できるようになると、ここで違いが生じますか? – skimon

+0

よろしくお願いします。実際、-m32では4.5.1もループ外の最初のバイトをコピーします。オフセットとして '%eax'を使用します。movzbl 1(%ecx、%eax)、%edx; movb%dl、1(%ebx、%eax); 64ビットモードの場合のようにポインタを直接インクリメントするのではなく、「addl 1、%eax'」を使用します。 VSコンパイラが64ビットで生成するコードを確認できますか?それが同様に振る舞うならば、アーキテクチャの可能性は非常に高いです。 –

+0

ちょうど64ビットのVSでチェックされても、それはまだ "最初のバイトをコピーして、0でないならループに入る"ということです。 – skimon

0

あなたの第二のコードは「再びヌルをチェック」しません。あなたの2番目のバージョンでは、サイクル本体は、edx+eax+1アドレスの文字(+1の部分に注意してください)の文字で動作します。これは数字1,2,3などです。プロローグコードは文字番号0で動作します。つまり、あなたが信じているように、コードは同じ文字を2回チェックしません。そこには「もう一度」はありません。

2番目のコードは、すでに説明したように、その機能が異なるため、より複雑なボットです(サイクルの最初の反復は効果的に取り除かれます)。ポインタの最終的な値は、あなたの拳と2番目のバージョンで異なります。

関連する問題

 関連する問題