varargs が実際に実装される方法と C 言語の制限により、次の場合を除き、呼び出す関数を...
取得せずに引数をコールチェーンに渡すことはできません。va_list
- コードを実行できるすべてのプラットフォームに適したアセンブリ言語を使用する
- コンパイラがどのように実装するかなどの詳細を知って
va_list
いる、または
- 引数の型のすべての可能な組み合わせを何らかの方法で計算し、それらを手動で渡す関数を作成してみてください。
これらのオプションのうち、(3) は現実的なケースでは明らかに非現実的であり、(2) はいつでも予告なしに変更される可能性があります。残りは (1)、コードが実行される各プラットフォームのアセンブリ言語です。
内部的には、varargs は各アーキテクチャの ABI 固有の方法で実装されます。概念的に...
は、「必要なすべての引数を、それらの引数を取る関数を呼び出しているかのように渡します。各引数をどこから取得するかは、あなた次第です」と述べています。i386
OS X や iOS シミュレータなど、すべての引数をスタックに渡すアーキテクチャの例を見てみましょう。
次の関数プロトタイプと呼び出しがあるとします。
void f(const char * const format, ...);
/* ... */
f("lUf", 0L, 1ULL, 1.0);
コンパイラは次のアセンブリを生成します (私が書いたとおりです。実際のコンパイラは、おそらく同じ効果を持つ多少異なる呼び出しシーケンスを生成します)。
leal L_str, %eax
pushl %eax
movl $0x3f800000, %eax
pushl %eax
movl $0x00000000, %eax
pushl %eax
movl $0x00000001, %eax
pushl %eax
movl $0x00000000, %eax
pushl %eax
call _f
これにより、各パラメーターが逆の順序でスタックにプッシュされます。秘密のトリックは次のとおりです。次のように宣言されていれば、コンパイラは同じことを行います。f()
void f(const char * const format, long arg1, unsigned long long arg2, float arg3);
これは、関数がスタックのパラメーター領域をコピーして vararg 取得関数を呼び出すことができる場合、引数は単純に通過することを意味します。問題: このパラメータ領域の大きさを把握する一般的な方法がありません! i386
では、フレーム ポインターを持つ関数からも呼び出されるフレーム ポインターを持つ関数では、バイトをチートしてコピーできますが、これrbp - *rbp
は非効率的であり、すべてのケースで機能しません (特に、パラメーターを受け取る関数やsをstruct
返す関数)。 struct
)。
次に、 と のようなアーキテクチャがarmv6
ありarmv7
、ほとんどのパラメーターは慎重に保存する必要があるレジスターに渡されx86_64
ます。パラメーターはレジスターに渡され、レジスターxmm
カウントは に渡されます。スタック ロケーションとレジスターの%al
両方がパラメーターにマップされます。ppc
a を使用せずに引数を転送する唯一の確実なva_list
方法は、コンパイラが行うのと同じ方法で、アーキテクチャごとにアセンブリを使用してコード内のアーキテクチャ ABI ロジック全体を再実装することです。
これも本質的に同じ問題をobjc_msgSend()
解決します。
「だから待って!」あなたは今言う。objc_msgSend
「このようにアセンブリをいじる代わりに、なぜ呼び出すことができないのですか?!」
回答: コンパイラに「スタック上で何かを壊したり、私が使用していないレジスタを消去したりしないでください」と伝える方法がないためです。サブクラスの実装でなんらかの作業を行う前に、呼び出しをスーパークラスの実装に転送するアセンブリ ルーチンを作成するobjc_msgSend()
必要が_stret
あり_fpret
ます。少なくとも 3 つのアーキテクチャ ( armv7
、i386
、x86_64
- および後方互換性と前方互換性の必要性に応じて、潜在的ppc
にppc64
、armv6
、 およびarmv7s
) での実装。
単純な varargs の場合、コンパイラは、呼び出しに関する詳細な知識とターゲットの呼び出し規則を使用して、va_list
. C では、この情報に直接アクセスすることはできません。そしてobjc_msgSend()
、Objective-C コンパイラとランタイムがすべてやり直すので、常に使用せずにメソッド呼び出しを記述できますva_list
。(また、一部のアーキテクチャでは、varargs 規則を使用するよりも、既知の呼び出しリストにパラメーターを渡すことができる方が効率的です)。
したがって、残念ながら、その価値よりもはるかに多くの労力を費やさなければ、それを行うことはできません。クラスの実装者は、これを教訓にしましょう。可変引数を取るメソッドを提供するときはいつでも、 のva_list
代わりに...
を受け入れる同じメソッドのバージョンも提供してください。とNSString
を使用した良い例です。initWithFormat:
initWithFormat:arguments: