16

私は過去に組み込みシステムのプロジェクトに取り組み、スタック変数の宣言の順序を再配置して、結果の実行可能ファイルのサイズを小さくしました。たとえば、次のような場合:

void func()
{
    char c;
    int i;
    short s;
    ...
}

これを次のように並べ替えます。

void func()
{
    int i;
    short s;
    char c;
    ...
}

アラインメントの問題により、最初のものでは 12 バイトのスタック スペースが使用され、2 番目のものでは 8 バイトしか使用されませんでした。

これは C コンパイラの標準的な動作ですか、それとも私たちが使用していたコンパイラの欠点ですか?

必要に応じて、コンパイラはスタック変数を並べ替えて、実行可能サイズを小さくすることができるようにすべきだと私には思えます。C標準のいくつかの側面がこれを妨げていることが示唆されていますが、どちらにしても信頼できるソースを見つけることができませんでした.

おまけの質問ですが、これは C++ コンパイラにも当てはまりますか?

編集

答えが「はい」の場合、C/C++ コンパイラはスタック変数を再配置できます。確実にこれを行うコンパイラの例を挙げていただけますか? これを裏付けるコンパイラのドキュメントまたは同様のものを見たいと思います。

再編集

助けてくれてありがとう。ドキュメントについては、2003 年の GCC サミット議事録で発表された Naveen Sharma と Sanjiv Kumar Gupta による論文Optimal Stack Slot Assignment in GCC (pdf)を見つけることができました。

ここで問題となっているプロジェクトは、ARM 開発に ADS コンパイラを使用していました。そのコンパイラのドキュメントには、ARM-Thumb アーキテクチャがローカル スタック フレームでアドレスを計算する方法により、私が示したような宣言の順序付けによってパフォーマンスとスタック サイズが向上することが記載されています。そのコンパイラは、これを利用するためにローカルを自動的に再配置しませんでした。ここにリンクされている論文によると、2003年現在、GCCはスタックフレームを再配置してARM-Thumbプロセッサの参照の局所性を改善していませんが、可能であることを暗示しています.

これが GCC で実装されたことを明確に示すものは何も見つかりませんが、この論文は、あなたがすべて正しいという証拠として数えられると思います。再度、感謝します。

4

11 に答える 11

40

コンパイラは、ローカル変数のスタック レイアウトを並べ替えるだけでなく、それらをレジスタに割り当てたり、レジスタやスタックにライブに割り当てたり、メモリ内の同じスロットに 2 つのローカル変数を割り当てたりすることができます (それらのライブ範囲が重複しない)、変数を完全に排除することさえできます。

于 2008-11-04T06:30:45.690 に答える
24

標準には C または C++ コンパイラに対してそれを禁止するものは何もないので、はい、コンパイラはそれを行うことができます。

相対的な順序を維持する必要がある集合体 (つまり、構造体) の場合は異なりますが、コンパイラは適切な配置を実現するためにパディング バイトを挿入する場合があります。

IIRC の新しい MSVC コンパイラは、ローカルのバッファ オーバーフローとの戦いでその自由を利用しています。

ちなみに、C++ では、コンパイラがメモリ レイアウトを並べ替えたとしても、破棄の順序は宣言の逆順でなければなりません。

(章と節を引用することはできませんが、これは記憶によるものです。)

于 2008-10-26T19:04:28.237 に答える
10

スタックが存在する必要さえありません (実際、C99 標準には「スタック」という単語が 1 つもありません)。そうです、コンパイラは、自動保存期間で変数のセマンティクスを保持する限り、自由にやりたいことを実行できます。

例として、ローカル変数がレジスターに格納されているため、デバッガーで表示できない状況に何度も遭遇しました。

于 2008-10-26T19:38:38.963 に答える
10

コンパイラは、変数のアドレスが取得/使用されていないことが分析で示された場合にのみ、スタックから変数を自由に削除して登録することさえできます。

于 2008-10-26T19:22:40.057 に答える
5

Texas Instruments 62xxシリーズのDSP用のコンパイラは、「プログラム全体の最適化」が可能であり、それを実行します。(オフにすることができます)

これは、ローカルだけでなく、コードが再配置される場所です。そのため、実行の順序は、期待したものとはまったく異なります。

CとC++は、実際にはメモリモデル(JVMと言う意味で)を約束していないため、状況はまったく異なり、それでも合法である可能性があります。

それらを知らない人のために、62xxファミリーはクロックサイクルDSPあたり8命令です。750Mhzでは、6e+9命令でピークになります。とにかく時々。これらは並列実行を行いますが、命令の順序付けは、Intel x86のようにCPUではなく、コンパイラで行われます。

PICとRabbitの組み込みボードには、特にうまく質問しない限り、スタックはありません。

于 2008-10-26T21:03:47.357 に答える
4

コンパイラは、データに対してスタックをまったく使用していない場合さえあります。8 バイト対 12 バイトのスタックについて心配しているほど小さなプラットフォームを使用している場合は、かなり特殊なアプローチを持つコンパイラーが存在する可能性があります。(いくつかの PIC および 8051 コンパイラが思い浮かびます)

どのプロセッサ用にコンパイルしていますか?

于 2008-10-26T19:28:19.873 に答える
0

C規格が何を要求するか、何を要求しないかについて、怠惰な憶測は必要ありません。最近のドラフトは、ANSI/ISOワーキンググループからオンラインで無料で入手できます。

于 2008-10-26T21:10:13.973 に答える
0

これはあなたの質問への回答ではありませんが、関連する問題についての私の 2 セントです...

スタック スペースの最適化の問題はありませんでしたが、スタック上の double 変数の位置合わせが正しくないという問題がありました。関数は他の関数から呼び出される可能性があり、スタック ポインターの値はアラインされていない値を持つ可能性があります。そこで、以下の案にたどり着きました。これは元のコードではなく、私が書いたものです...

#pragma pack(push, 16)

typedef struct _S_speedy_struct{

 double fval[4];
 int64  lval[4];
 int32  ival[8];

}S_speedy_struct;

#pragma pack(pop)

int function(...)
{
  int i, t, rv;
  S_speedy_struct *ptr;
  char buff[112]; // sizeof(struct) + alignment

  // ugly , I know , but it works...
  t = (int)buff;
  t +=  15; // alignment - 1
  t &= -16; // alignment
  ptr = (S_speedy_struct *)t;

  // speedy code goes on...
}
于 2008-10-28T15:28:42.227 に答える
0

まともなコンパイラは、可能であればローカル変数をレジスタに入れます。変数をスタックに配置する必要があるのは、レジスタに過剰な負荷がかかる (十分なスペースがない) 場合、または変数のアドレスが取得された場合 (メモリ内に存在する必要があることを意味します) のみです。

私の知る限り、変数を C/C++ のスタック上の特定の場所またはアラインメントに配置する必要があると言うものは何もありません。コンパイラは、パフォーマンスに最適な場所、および/またはコンパイラの作成者にとって便利な場所にそれらを配置します。

于 2008-10-26T19:30:21.493 に答える
0

私の知る限り、C または C++ の定義には、コンパイラがスタック上のローカル変数を順序付ける方法を指定するものは何もありません。この場合、コンパイラーの動作に依存するのは悪い考えだと思います。コンパイラーの次のバージョンでは異なる動作をする可能性があるからです。スタックの数バイトを節約するためにローカル変数を注文するのに時間と労力を費やす場合、それらの数バイトはシステムの機能にとって非常に重要です。

于 2008-10-26T19:43:38.143 に答える
0

それはコンパイラの仕様であり、必要に応じて逆のことを行う独自のコンパイラを作成できます。

于 2008-10-26T18:58:19.030 に答える