1

va_start()、va_arg() マクロの背後にあるものを把握しようとしています。以下のコードはうまく機能します。

#include <iostream>
#include <cstdarg>

void f(double a, double b, ...)
{
   va_list arg;
   va_start(arg, b);
   double d;
   while((d = va_arg(arg, double)) != 0)
      {
         std::cout << d << '\n';
      }
}

int main(int argc, char *argv[])
{
   f(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 0.0);
   return 0;
}

予想どおり、次のような出力が得られました: 3 4 5 6 7 8 9. 次に、そのマクロの定義を見つけました (インターネットでは、ヘッダー stdarg.h が不思議なため、マクロ va_arg(v, l) を _builtin_va_arg( のように定義します) v、l)、最後は定義されておらず、stdarg.hには何も含まれていないため、ライブラリにあるのですか???)。それにもかかわらず、「cstdarg」の代わりに私は書きました:

typedef char* va_list;

#define _INTSIZEOF(n) \
((sizeof(n)+sizeof(int)-1) &~(sizeof(int)-1))

#define va_start(ap,v) \
(ap = (va_list)&v + _INTSIZEOF(v))

#define va_arg(ap,t) \
(*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))

#define va_end(ap) (ap = (va_list)0)

出力は 1 -0.0409377 -0.0409377 4.88084e-270 4.85706e-270 1 2 3 4 5 6 7 8 9 のように奇妙になりました。可変引数は最後に宣言されたパラメータのすぐ隣に配置されていると思いましたが、どうやらもっと複雑なパラメータがあるようです状況。どこが間違っているのか、実際に何が起こっているのかを誰かが明らかにしてくれたら、とてもうれしいです。

4

2 に答える 2

1

手書きのマクロは、すべての引数を常にスタックに渡し、小さい型を sizeof(int) にパディングするマシンで動作します。しかし、多くのマシン (最近ではほとんど?) は引数をスタックに渡しません。代わりに引数をレジスタに渡し、スタックが多すぎてレジスタに収まらない場合にのみスタックを使用します。

そのため、va_args を処理するために、コンパイラは ABI と、どの引数がどのような状況でどこに配置されるかを知る必要があります。一般的に行われるのは、va_list に多数の配列 (引数を含む可能性のあるすべてのレジスターを保持するのに十分な数) と多数のポインター (通常、レジスターのタイプごとに 1 つとスタック用に 1 つ) を含めることです。引数が配列に登録され、ポインターが初期化され、次に va_arg が、指定された引数の型が渡されるレジスタの種類を判断し、適切な場所から値を引き出します. したがって、整数/ポインター引数用に 8 つのレジスタを持つ仮想プロセッサの場合、 float/double 引数用の 8 つのレジスタ、次のようなものがあるかもしれません:

typedef struct {
    intptr_t iregs[8], *iptr;
    double   fregs[8], *fptr;
    char     *spptr;
} va_list;

inline void _builtin_va_start(va_list &ap, arg) {
    // dump the registers might be used to pass args into ap->iregs and ap-fregs,
    // setup iptr and fptr to point into iregs and fregs after the arguments that
    // correspond to 'arg' and those before it.  spptr points into the stack space
    // used for arguments after regs run out
}
inline _builtin_va_arg(va_list &ap, type) {
    if (type is integer or pointer) {
        if (ap->iptr == ap->iregs+8) {
            rv = *(type *)ap->spptr;
            ap->spptr += sizeof(type);
        } else {
            rv = *ap->iptr++;
        }
    } else if (type is float or double) {
        if (ap->fptr == ap->fregs+8) {
            rv = *(type *)ap->spptr;
            ap->spptr += sizeof(type);
        } else {
            rv = *ap->fptr++;
        }
    } else {
        // some other type (struct?) deal with it
    }
}

これらの_builtin_va関数はどちらも C で記述できないことに注意してください。コンパイラに組み込む必要があります

于 2013-03-29T23:50:18.327 に答える
1

、および忘れないでくださいはva_start、コンパイラに固有のものです。他の場所からそれらを取得して、それらが機能することを期待することはできません。それらを使用する際はマニュアル ページに従ってください。コンパイラ エンジニアである場合にのみ、内部の仕組みを理解しようとする必要があります。va_argva_end

PS: ああ、それらの定義は通常、微妙なトリックを使用して機能させるために非常に神秘的です。

P.S2: が定義されている場所に関する質問への回答_builtin_va_arg: コンパイラによって知られている、いわゆるbuiltinです。コンパイラのソースで見つけることができます ;)

于 2013-03-29T23:09:16.083 に答える