だから、私はアセンブリを学んでいて、私は ABIs を知っているようになり、 cdecl 呼び出し規約を使用して動作するいくつかの基本テストを取得し、nasmの下でcのstdlibを使用します。しかし、他の呼び出し規約(topspeed/Clarion/JPI/watcom/borland register(delphi)、fastcallなど)を見てきました。そして、Clarionの代わりにcdeclを使用することの実際の利点は何でしょうか。より具体的には、レジスタを使用する代わりにプッシュする。
これは私が想像した利点の一部ですどれが当てはまるか教えてください。
Cdeclは可変パラメーターを使用できるため重要であると読みましたが、これを見ると、レジスターを使用して同じことができます。問題は、パラメーターの数、タイプ、および順序を知ることですが、その問題はcdeclにも存在します。フォーマット文字列(printf内)または関数のシグネチャから推測できます。そして、レジスタが足りなくなったら、残りのパラメーターをプッシュできます。
Cpusの製造元が市長の使用の呼び出し規約に関係なく、可能な限りすべてを最適化している可能性があるため、パフォーマンスはそれほど大きなものではないはずです(今回はcdecl?¿?)。しかし、生の操作(およびキャッシュとライトバックバッファーを無視)を考える場合、Ithinkスタックの代わりにレジスタを使用する方が高速です(これはラムにありますか?)つまり、「inc eax」対「add eax、1」のようなものです(私は正しいですか?)。
押すことで、(ローカル変数を作成するのと同じように)retすると消えるメモリ空間を作成します。それは便利なように思えるかもしれません(呼び出されるときまでにローカル変数を持つため)。
しかし、ほとんどのパラメータは(私の経験では)読み取り値として使用されており、変異した値を格納するためにそれらを変数として必要とすることは非常に少ないため、実際に複雑な構造を扱っている場合、とにかくポインタとして渡されます。
実際にメモリスペースを必要とする前にメモリスペースを作成することの価値は、実際にはわかりません。
私が見るように、私がdo変数を必要とする場合、オプションそれを作成するために(クラリオンのように)、しかし私がしない場合できればnotそれを作成します(cdeclではできません)。
レジスタを保存します。
私はポジティブな側面さえ考えることができません。私の知る限り、レジスタは中間計算に使用され、そのため本質的に揮発性です。それらを揮発性であると考える場合、値を「保持」する必要があるときはいつでもそれらをプッシュ/ポップ/移動でき、そのような場合はonlyです。その意味で、私はそれを最も効率的な使用法と見なしています(私は必要なときにのみRAMにアクセスします)。
しかし、それを行わない場合は、「レジスターを保存する」ようにしてください:
呼び出し先:呼び出し先コードがレジスターで何をするかは、はっきりとはわかっていません(文書化されていないか、同じ呼び出し規約に準拠している場合を除きます)。それらが保存されることを期待することで、呼び出し先に人為的な制約を課します。呼び出し先は、どのレジスタをreally保持する必要があるかわからないため、不要なレジスタを過剰に保持する傾向があります。 (x86のpusha/popaのような?)どれがsounds本当に私にとって非効率的です。
呼び出し元:呼び出し元は、呼び出し先が[s] taint?[/ s]を使用するレジスタを認識していないため、すべてのレジスタを保持します。以前と同じ非効率的な結果になります。
Linux syscallsと8086(以前のクラス)は、スタックではなくレジスターを使用してパラメーターを渡すことに気付きました。そこで何が起こった?
したがって、それらは私の考えです。可能な限りの説明に感謝します。
ノート:
Raymond Chenが 呼び出し規約の歴史 をここにまとめました。彼はClarionに触れていませんが、Fastcallに触れています。これはClarionとは異なりますが、レジスターベースのアプローチをより多く使用しています。
彼はこれを言う必要があります:
Fastcall(__ fastcall)
Fastcall呼び出し規約では、DXレジスタの最初のパラメータとCXレジスタの2番目のパラメータを渡します(私はそう思います)。これが実際に高速であるかどうかは、通話の使用状況によって異なります。レジスターで渡されたパラメーターをスタックにスピルして、呼び出し先が再ロードする必要がないため、一般的に高速でした。一方、第1パラメーターと第2パラメーターの計算の間に重要な計算が発生した場合、呼び出し側はとにかくそれをこぼさなければなりません。傷害に侮辱を加えるために、呼び出された関数はレジスタを別の何かのためにスペアにする必要があるため、レジスタをメモリにこぼしました。これは、「最初の2つのパラメータ間の重要な計算」の場合、二重こぼれを意味します。痛い!
その結果、__ fastcallは通常、短いリーフ関数に対してのみ高速でしたが、それでも高速ではなかった可能性があります。
ここで適用された批判は依然として関連性があると思います-クラリオンは特定の種類の通話に対してはおそらく高速ですが、他の種類の通話に対してはそうではありません。
そうは言っても、レジスタの使用に関するあなたのポイントは非常に有効です。質問の範囲でx64を検討したくなかったのですが、議論されたパターン Itaniumについてはそのシリーズの後半 に興味があるかもしれません!
呼び出し規約は、プラットフォームによって多少異なります。 Linuxでは、GCCがデファクトスタンダードを提供します。 このページ は、Linuxでのさまざまな呼び出し規約の長所と短所について少し詳しく説明しています(少し誇張されていますが)。
多くの場合、Cライブラリ(libc)を使用することが唯一の方法であり、直接のシステムコールは不適切であると言われます。これはある程度、本当です。一般に、libcは神聖ではないことを知っておく必要があります。ほとんどの場合、libcはいくつかのチェックのみを行い、次にカーネルを呼び出してから、errnoを設定します。これはプログラムでも簡単に行うことができ(必要な場合)、プログラムは数十倍小さくなり、共有ライブラリを使用していないため(静的バイナリの方が高速であるため)、パフォーマンスも向上します。 )
Windowsの世界では、多くの呼び出し規約があり、その一部はMicrosoft固有のものであり、一部はサポートされなくなりました。 このページ は、それぞれの長所と短所のいくつかを説明しています。特に:
__ cdeclは、CおよびC++プログラムのデフォルトの呼び出し規約です。スタックは呼び出し元によってクリーンアップされるため、vararg関数を実行できます。 __ cdecl呼び出し規約では、各関数呼び出しにスタッククリーンアップコードを含める必要があるため、__ stdcallよりも大きな実行可能ファイルが作成されます。
__ stdcall呼び出し規約は、Win32 API関数を呼び出すために使用されます。呼び出し先がスタックをクリーンアップするため、コンパイラーは可変引数関数__ cdeclを作成します。この呼び出し規約を使用する関数には、関数プロトタイプが必要です。