2016-05-12 7 views
7

は、Iは固定サイズのアレイメンバーとC/C++でstructを有すると仮定すると、例えば:コンパイラは、固定サイズの配列を持つ構造体を返す関数を最適化しますか?

#define SIZE 10000 
struct foo{ 
    int vector_i[SIZE]; 
    float vector_f[SIZE]; 
}; 

及びIはfooのインスタンスを返す関数を作成したい、等:

foo func(int value_i, float value_f){ 
    int i; 
    foo f; 
    for(i=0;i<SIZE;i++) f.vector_i[i] = value_i; 
    for(i=0;i<SIZE;i++) f.vector_f[i] = value_f; 
    return f; 
} 

私が使用して関数を呼び出す場合:

foo ff = func(1,1.1); 

コンパイラは最適化のいくつかの種類(すなわちTCO)を実行しますか?

実行ファイルは直接ff変数を記入します、またはそれはfuncの最初fを記入して、fffからすべての値をコピーしますか?

最適化が実行されているかどうかを確認するにはどうすればよいですか?

+2

これは、ローカル変数として保持することは非常に巨大なオブジェクトです。私が正しく計算したら、それは625kb(32bitプラットフォーム)でなければなりません。Windows上では各スレッドのスタックが最大1MBになると思います。 – GeorgeAl

+3

代わりに引数として構造体にポインタ(または参照)を渡します。それについて心配する必要はありません。 –

+2

生成されたアセンブラを見ることができます。 C++では、コピーコンストラクタを定義し、それが呼び出されるかどうかを確認することもできます。 –

答えて

6

私の答えはC++に当てはまります。

コンパイラは何らかの最適化(TCO)を実行しますか?

TCOでは「テールコールの最適化」を意味していますか?この関数は、最後に関数呼び出しを行いません(テールコール)。最適化は適用されません。

コンパイラelide名前付き戻り値の最適化による戻り値から一時値へのコピー。一時的なコピー初期化も省略することができます。最適化が行われた場合に


はどうすれば確認できますか?

生成されたアセンブリコードを読み取ります。

アセンブリを読み取れない場合は、副作用のあるコンストラクタをコピーして移動し、その副作用が発生するかどうかを確認する方法もあります。ただし、プログラムを変更すると、コンパイラが最適化を決定するかどうかに影響を与える可能性があります(副作用はコピー・エッセンスを防ぐために必要ありません)。あなたが最適化に依存したくない場合は


は、明示的に参照(Cでのポインタ)で関数に出たオブジェクトを渡し、その場所でそれを変更する必要があります。特定の基準が満たされた場合


コピーの省略のための標準参照[class.copy] §31 (current standard draft)

、実装は/コンストラクタはコピーのために選択された場合でも、クラスオブジェクトのコピー/移動する構成を省略することが許可されています移動操作および/またはオブジェクトのデストラクタは副作用を有する。 [...]

このセクションでは、この場合に満たされる基準について説明します。この見積もりは、2016-04-07の標準書類草案から生成されたものです。標準文書のバージョンによって番号が異なる場合があり、規則が少し変更されています。クォートされた部分はC++ 03以降は変更されていません。セクションは[class.copy]§15です。

+0

これらの副作用を呼び出さないでコードの正当性に違反することはありませんか? C++についてはわかりませんが、Cでは、コンパイラの最適化によって変更してはならない「観測可能な振舞い」と呼ばれるものがあります。 – Olaf

+3

@Olaf copy elisionには、if-ifルールを無視する特別な例外があります。私はcについて知らないので、免責事項を追加しました(ただし、コピーはコピー自体に加えて副作用を伴うことはありません。 – user2079303

+0

@ user2079303 C言語でのコピーには副作用がないので、そこには問題はありません。 – molbdnilo

2

これは、Agner Fog's Calling Conventions documentの7.1章で説明されています。構造体、クラス、および共用体を返すメソッド。

struct、classまたはunionオブジェクトは、十分に小さく複雑すぎない場合にのみ、レジスタ内の関数から返すことができます。オブジェクトが複雑すぎるか、適切なレジスタに収まらない場合、呼び出し側はオブジェクトの記憶領域を確保し、この領域へのポインタを関数のパラメータとして渡す必要があります。ポインタはレジスタまたはスタックに渡すことができます。同じポインタが関数によって返されます。詳細な規則は表7に示されています。

つまり、呼び出し元のバッファ(呼び出し元のスタック)に大きな戻りオブジェクトが直接構築されます。

復帰へのオブジェクトのアイデンティティはコンパイル時に知られていない場合は、余分なコピーがまだ例えば、必要とされる:

foo func(bool a) { 
    foo x, y; 
    // fill x and y 
    return a ? x : y; // copying is required here 
} 
関連する問題