2

可変個引数関数があるとしましょう。foo関数foo(int tmp, ...)を呼び出すときは、引数がいくつあるかを知る必要があります。私は、引数がいくつあるかを知る2つの方法を知っています。

  1. fooを呼び出すときは、-1のように最後の引数を使用します。これにより、関数呼び出しは次のようになりfoo(tmp, 1, 2, 9, -1)ます。foo内にいて、va_arg呼び出しが-1を返す場合、すべての関数引数を読んだことがわかります。

  2. プログラマーが引数の総数を持つfooにもう1つの引数を追加すると、次のようにfooが呼び出されます。foo(tmp, 5, 1, 2, 3, 4, 5)またはfoo(tmp, 2, 7, 8)

私は最初の方法をたどっていましたが、かつて次のバグがありました。コードで:

expr_of_type(expr, boolexpr_e, newtable_e, nil_e, -1)

ここで、expr_of_typeは可変個引数関数であり、expr(最初の引数)が次のタイプのいずれかであるかどうかをチェックしていました(boolexpr_eまたはnew_table_eまたはnil_eはすべてのタイプの列挙型でした)。私は誤って書いた:

expr_of_type(expr, boolexpr_e, newtable_e, nil_e -1)

nil_eと-1の間のコンマを忘れました。これは、nil_eが列挙型であり、nil_e -1が有効な式であり、nil_eが0ではなかったため、expr_of_type引数を取得しようとしたときに、指定された可変個引数が最後の引数として-1を検出しなかったためです。検索を続けてバグを作成しましたが、見つけるのに少し時間がかかりました。

可変個引数関数にもう1つの引数を追加または削除するときは、引数の総数を含むパラメーターを変更する必要があるため、2番目の方法も適切ではありません。

可変個引数関数を使用/作成するためのより良い方法を探しているときに、最初の方法を使用したときに発生したバグを解決できる可変個引数マクロを見つけました。ただし、可変個引数マクロはC99標準で使用できます。C89で可変個引数関数を使用/作成するためのより良い方法を探していました。何か案は?

4

3 に答える 3

6

一般に、センチネル値または明示的なカウントを介して、何らかの方法で引数カウントを渡す必要があります。

ただし、より良いセンチネルを作成することで、センチネルの問題を解決できます。これが、負の定数に展開されるプリプロセッサ マクロを括弧で囲む必要がある理由の 1 つです。

#define VARARG_SENTINEL (-1)

その後nil_e VARARG_SENTINEL、コンパイルエラーが発生します。

enumorを使用してconst intも機能します。

enum { VARARG_SENTINEL = -1 };

センチネル値に記号定数を使用することは、他の理由でも優れています (より自己文書化され、後で基になる値を変更しやすくなります)。

于 2010-07-17T17:25:48.607 に答える
1

C99 をコンパイルする場合は、変数引数を明示的に渡さなくても可変引数を指定するために可変長マクロを使用できます。

#include <stdio.h>
#include <stdarg.h>
 
void _foo(size_t n, int xs[])
{
    for(int i=0 ; i < n ; i++ ) {
        int x = xs[i];
        printf("%d\n", x);
    }        
}
 
#define foo(arg1, ...) do {            \
   int _x[] = { arg1, __VA_ARGS__ };   \
   _foo(sizeof(_x)/sizeof(_x[0]), _x); \
} while(0)
 
int main()
{
    foo(1, 2, 3, 4);
    foo(-1, -2, -3, -4, -5, -6, -7);
    return 0;
}

出力:

1
2
3
4
-1
-2
-3
-4
-5
-6
-7

ただし、これにより値を返すことができなくなります。gcc 拡張機能を使用して値を返すことができます。

#include <stdio.h>
#include <stdarg.h>
 
int _foo(size_t n, int xs[])
{
    int i;
    for(i=0 ; i < n ; i++ ) {
        int x = xs[i];
        printf("%d\n", x);
    }        
    return n;
}
 
#define foo(arg1, ...) ({              \
   int _x[] = { arg1, __VA_ARGS__ };   \
   _foo(sizeof(_x)/sizeof(_x[0]), _x); \
})
 
int main()
{
    int x = foo(1, 2, 3, 4);
    printf("foo returned %d\n", x);
    x = foo(-1, -2, -3, -4, -5, -6, -7);
    printf("foo returned %d\n", x);
    return 0;
}

出力:

1
2
3
4
foo returned 4
-1
-2
-3
-4
-5
-6
-7
foo returned 7

しかし、もちろん、マクロは死んでいます。マクロ万歳!

編集:

おっと、OPを十分に注意深く読んでいませんでした。ごめん!

于 2010-07-17T19:02:13.450 に答える
0

また、動的構造を使用することにより、完全に可変個引数のパラメーターを回避する可能性も常にあります。

struct vararray {
   uint_t n;
   uint_t params[0];
};

void foo(int tmp, struct varray *pVA);

unionさまざまなサイズの構造体で複雑化することもできます。

かつて、この種のアプローチを使用した特定のAPIを備えた組み込みコントローラーがありました。これは、イベントハンドラーに渡されるunion固定サイズのアプローチです。struct特定の型を使用でき、可変個引数関数のパラメーター型チェックがないことを忘れてはならないため、コンパイラーは関数パラメーターの型をより適切にチェックできるため、いくつかの利点がありました。

于 2010-07-17T18:05:01.733 に答える