0

こんにちは私はこれが非常にばかげた/基本的な質問であることを知っていますが、次のようなコードの違いは何ですか:-

int *i;
for(j=0;j<10;j++)
  {
    i = static_cast<int *>(getNthCon(j));
    i->xyz
  }

そして、このようなもの:-

for(j=0;j<10;j++)
  {
    int *i = static_cast<int *>(getNthCon(j));
    i->xyz;
  }

つまり、これらのコードはロジックが非常に同じですか、それともそのローカルな性質のために違いがありますか?

4

5 に答える 5

1

実用的な違いの1つは、のスコープですi。最初のケースでiは、ループの最後の反復後も存在し続けます。第二にそれはしません。

iすべての計算が完了した後の値を知りたい場合があります。その場合は、2番目のパターンを使用してください。

あまり実用的ではない違いは、=それぞれの場合のトークンの性質です。最初の例では、割り当てi = ...を示しています。2番目の例では、は初期化を示します。一部のタイプ(またはではない)は、代入と初期化を異なる方法で処理する場合があります。int *i = ...int*fp_ContainerObject*

于 2012-07-11T21:13:23.153 に答える
0

正常な最適化コンパイラの場合、メモリ割り当ての点で違いはありません。唯一の違いは、のスコープですi。これがサンプルプログラムです(そして、はい、ここにリークがあることに気づきました):

#include <iostream>

int *get_some_data(int value) {
    return new int(value);
}

int main(int argc, char *argv[]){
    int *p;
    for(int i = 0; i < 10; ++i) {
        p = get_some_data(i);
        std::cout << *p;
    }

    return 0;
}

そして、生成されたアセンブリ出力:

int main(int argc, char *argv[]){   
01091000  push        esi  
01091001  push        edi  
    int *p;
    for(int i = 0; i < 10; ++i) {
01091002  mov         edi,dword ptr [__imp_operator new (10920A8h)]  
01091008  xor         esi,esi  
0109100A  lea         ebx,[ebx]  
        p = get_some_data(i);
01091010  push        4  
01091012  call        edi  
01091014  add         esp,4  
01091017  test        eax,eax  
01091019  je          main+1Fh (109101Fh)  
0109101B  mov         dword ptr [eax],esi  
0109101D  jmp         main+21h (1091021h)  
0109101F  xor         eax,eax  
        std::cout << *p;
01091021  mov         eax,dword ptr [eax]  
01091023  mov         ecx,dword ptr [__imp_std::cout (1092048h)]  
01091029  push        eax  
0109102A  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (1092044h)]  
01091030  inc         esi  
01091031  cmp         esi,0Ah  
01091034  jl          main+10h (1091010h)  
    }

これで、ループ内でポインタが宣言されました。

int main(int argc, char *argv[]){   
008D1000  push        esi  
008D1001  push        edi  
    for(int i = 0; i < 10; ++i) {
008D1002  mov         edi,dword ptr [__imp_operator new (8D20A8h)]  
008D1008  xor         esi,esi  
008D100A  lea         ebx,[ebx]  
        int *p = get_some_data(i);
008D1010  push        4  
008D1012  call        edi  
008D1014  add         esp,4  
008D1017  test        eax,eax  
008D1019  je          main+1Fh (8D101Fh)  
008D101B  mov         dword ptr [eax],esi  
008D101D  jmp         main+21h (8D1021h)  
008D101F  xor         eax,eax  
        std::cout << *p;
008D1021  mov         eax,dword ptr [eax]  
008D1023  mov         ecx,dword ptr [__imp_std::cout (8D2048h)]  
008D1029  push        eax  
008D102A  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (8D2044h)]  
008D1030  inc         esi  
008D1031  cmp         esi,0Ah  
008D1034  jl          main+10h (8D1010h)  
    }

ご覧のとおり、出力は同じです。デバッグビルドでも、アセンブリは同一のままであることに注意してください。

于 2012-07-11T21:25:20.697 に答える
0

Ed S.は、ほとんどのコンパイラが両方の場合で同じコードを生成することを示しています。しかし、Maheshが指摘しているように、それらは実際には同一ではありません(iバージョン1ではループスコープの外で使用することが合法であるがバージョン2では使用できないという明白な事実を超えても)。誤解を招かない方法で、これらの両方がどのように真実であるかを説明しようと思います。

まず、ストレージはiどこから来るのですか?

標準はこれについては言及していません。ストレージがスコープの存続期間全体にわたって利用可能である限りi、コンパイラが好きな場所であればどこでもかまいません。ただし、ローカル変数(技術的には、自動保存期間のある変数)を処理する一般的な方法は、適切なスコープのスタックフレームをsizeof(i)バイト単位で拡張し、そのスタックフレームにiオフセットとして保存することです。

「ティーチングコンパイラ」は、常にスコープごとにスタックフレームを作成する場合があります。しかし、実際のコンパイラは通常、特にループスコープの出入りで何も起こらない場合は気になりません。(アセンブリを確認するか、デバッガーに割り込む以外に違いを判断する方法はありません。もちろん、これを行うことは許可されています。)したがって、両方のバージョンは、おそらく、iからのまったく同じオフセットを参照することになります。関数のスタックフレーム。(実際には、それiはレジスターに入れられる可能性が非常に高いですが、それはここで重要なことを何も変更しません。)

それでは、ライフサイクルを見てみましょう。

最初のケースでは、コンパイラはi関数スコープで宣言されている場所をデフォルトで初期化し、ループを通過するたびにコピーして割り当て、関数スコープの最後で破棄する必要があります。2番目のケースでは、コンパイラはi各ループの開始時にコピー初期化を行い、各ループの終了時にそれを破棄する必要があります。このような:

iクラスタイプの場合、これは非常に重要な違いになります。(理由が明らかでない場合は、以下を参照してください。)しかし、そうではありません。それはポインターです。これは、デフォルトの初期化と破棄が両方とも操作なしであり、コピーの初期化とコピーの割り当てが同じであることを意味します。

したがって、ライフサイクル管理コードはどちらの場合も同じになります。ループを通過するたびに1回コピーされ、それだけです。

言い換えると、ストレージは同じであることが許可されており、おそらく同じになるでしょう。ライフサイクル管理は同じである必要があります。

私がクラスタイプの場合、なぜこれらが異なるのかを説明することを約束しました。

この擬似コードを比較します。

i.IType();
for(j=0;j<10;j++) {
  i.operator=(static_cast<IType>(getNthCon(j));
}
i.~IType();

これに:

for(j=0;j<10;j++) {
  i.IType(static_cast<IType>(getNthCon(j));
  i.~IType();
}

一見すると、最初のバージョンは1 IType()、10 operator =(IType&)、および1〜IType()であり、2番目のバージョンは10 IType(IType&)および10〜IType()であるため、「より良い」ように見えます。また、一部のクラスでは、これが当てはまる場合があります。しかし、operator =がどのように機能するかを考えると、通常、少なくともコピーの構築と破棄と同等のことを行う必要があります。

したがって、ここでの本当の違いは、最初のバージョンにはデフォルトのコンストラクターとコピー代入演算子が必要ですが、2番目のバージョンには必要ないということです。そして、そのstatic_castビットを取り出すと(つまり、コピーではなく変換コンストラクターと割り当てについて話している)、見ているものはこれと同等です。

for(j=0;j<10;j++) {
  std::ifstream i(filenames[j]);
}

明らかに、その場合はループからiを引き出そうとします。

しかし、繰り返しますが、これは「ほとんどの」クラスにのみ当てはまります。バージョン2が途方もなく悪く、バージョン1の方が理にかなっているクラスを簡単に設計できます。

于 2012-07-11T22:42:57.573 に答える
0

それらの間にほとんど違いはありません。

最初のコードサンプルでiは、​​はループの外側で宣言されているため、各反復で同じポインター変数を再利用しています。2番目でiは、はループの本体にローカルです。

iはループの外部では使用されず、1回の反復で割り当てられた値は将来の反復では使用されないため、2番目のサンプルのように、ローカルで宣言することをお勧めします。

ちなみに、iはポインタ変数の悪い名前です。これは通常、int変数、特にforループで使用される変数に使用されます。

于 2012-07-11T21:12:56.700 に答える
-2

反復ごとに、2番目のケースでは、新しいポインター変数がスタックに作成されます。最初のケースでは、ポインタ変数は1回だけ(つまり、ループに入る前に)作成されます。

于 2012-07-11T21:08:01.660 に答える