web-dev-qa-db-ja.com

可変数の引数を受け入れる関数に渡された引数の数をカウントする方法は?

次のプログラムで関数に渡された引数の数を数える方法:

#include<stdio.h>
#include<stdarg.h>
void varfun(int i, ...);
int main(){
        varfun(1, 2, 3, 4, 5, 6);
        return 0;
}
void varfun(int n_args, ...){
        va_list ap;
        int i, t;
        va_start(ap, n_args);
        for(i=0;t = va_arg(ap, int);i++){
               printf("%d", t);
        }
        va_end(ap);
}

このプログラムの出力は、ubuntu 10.04でのgccコンパイラーを介しています。

234561345138032514932134513792

どれだけの数を見つけるか関数に実際に渡される引数の数?

39
codeomnitrix

できません。なんらかの方法で引数の数を示すために、呼び出し元を管理する必要があります。あなたはできる:

  • 引数の数を最初の変数として渡します
  • 最後の変数の引数がnull、ゼロ、またはその他のものであることを要求する
  • 最初の引数に期待される内容を記述させます(たとえば、printf形式の文字列は、後に続く引数を指示します)
51
Alexandre C.

この戦略を使用して、 別の回答 から盗まれて微調整されたチートをプリプロセッサに任せることができます。

#include <stdio.h>
#include <stdarg.h>

#define PP_NARG(...) \
         PP_NARG_(__VA_ARGS__,PP_RSEQ_N())
#define PP_NARG_(...) \
         PP_ARG_N(__VA_ARGS__)
#define PP_ARG_N( \
          _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
         _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
         _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
         _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
         _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
         _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
         _61,_62,_63,_64,_65,_66,_67,_68,_69,_70, \
         _71,_72,_73,_74,_75,_76,_77,_78,_79,_80, \
         _81,_82,_83,_84,_85,_86,_87,_88,_89,_90, \
         _91,_92,_93,_94,_95,_96,_97,_98,_99,_100, \
         _101,_102,_103,_104,_105,_106,_107,_108,_109,_110, \
         _111,_112,_113,_114,_115,_116,_117,_118,_119,_120, \
         _121,_122,_123,_124,_125,_126,_127,N,...) N
#define PP_RSEQ_N() \
         127,126,125,124,123,122,121,120, \
         119,118,117,116,115,114,113,112,111,110, \
         109,108,107,106,105,104,103,102,101,100, \
         99,98,97,96,95,94,93,92,91,90, \
         89,88,87,86,85,84,83,82,81,80, \
         79,78,77,76,75,74,73,72,71,70, \
         69,68,67,66,65,64,63,62,61,60, \
         59,58,57,56,55,54,53,52,51,50, \
         49,48,47,46,45,44,43,42,41,40, \
         39,38,37,36,35,34,33,32,31,30, \
         29,28,27,26,25,24,23,22,21,20, \
         19,18,17,16,15,14,13,12,11,10, \
         9,8,7,6,5,4,3,2,1,0

void _variad(size_t argc, ...);
#define variad(...) _variad(PP_NARG(__VA_ARGS__), __VA_ARGS__)

void _variad(size_t argc, ...) {
    va_list ap;
    va_start(ap, argc);
    for (int i = 0; i < argc; i++) {
        printf("%d ", va_arg(ap, int));
    }
    printf("\n");
    va_end(ap);
}

int main(int argc, char* argv[]) {
    variad(2, 4, 6, 8, 10);
    return 0;
}

ここにはいくつかの巧妙なトリックがあります。

1)variadic関数を直接呼び出す代わりに、引数をカウントし、引数カウントを関数の最初の引数として渡すマクロを呼び出しています。 mainのプリプロセッサの最終結果は次のようになります。

_variad(5, 2, 4, 6, 8, 10);

2)PP_NARGは引数を数えるための賢いマクロです。

ここでの主力はPP_ARG_Nです。最初の127個の引数(任意に_1_2_3などと命名)を無視し、128番目の引数にNという名前を付け、その結果を定義することにより、128番目の引数を返します。マクロはNになります。

PP_NARGは、PP_ARG_Nで連結された__VA_ARGS__PP_RSEQ_Nを呼び出します。これは、127から0までカウントされる逆のシーケンスです。

引数を指定しない場合、PP_RSEQ_Nの128番目の値は0です。1つの引数をPP_NARGに渡すと、その引数はPP_ARG_Nとして_1に渡されます。 _2は127で、PP_ARG_Nの128番目の引数は1です。したがって、__VA_ARGS__の各引数はPP_RSEQ_Nに1つずつぶつかり、128番目に正しい答えが残ります。スロット。

(どうやら 127個の引数はCが許す最大値です 。)

14
Dan Fabulich

できません。他の何かがあなたに伝えなければなりません(たとえば、printfの場合、フォーマット文字列内のフォーマット記述子の数によって暗示されます)

7

C99準拠のコンパイラ(プリプロセッサを含む)がある場合、引数の数を計算するマクロを宣言することでこの問題を回避できます。これを自分で行うのは少し注意が必要です。P99_VA_ARGSから P99マクロパッケージ これを達成するために。

5
Jens Gustedt

できません。可変引数は、これを可能にするようには設計されていません。他のメカニズムを実装して、引数の数を関数に伝える必要があります。一般的な選択肢の1つは、パラメーターリストの最後にセンチネル引数を渡すことです。例:

varfun(1, 2, 3, 4, 5, 6, -1);

もう1つは、最初にカウントを渡すことです。

varfun(6, 1, 2, 3, 4, 5, 6);

これはクリーンですが、最後にセンチネルを覚えて維持するよりも、カウントを間違えたり、更新を忘れたりする方が簡単なので、安全ではありません。

どのように行うかはあなた次第です(printfのモデルでは、フォーマット文字列が引数の数(およびタイプ)を決定します)。

4
Marcelo Cantos

最も安全な方法は上記のとおりです。ただし、前述の余分な引数を追加せずに引数の数を本当に知る必要がある場合は、この方法で行うことができます(ただし、非常にマシン依存、OS依存、まれにコンパイラ依存であることにも注意してください)。 64ビットのDell E6440でVisual Studio 2013を使用してこのコードを実行しました。

もう1つのポイントは、sizeof(int)で除算した時点で、すべての引数がintであったためです。サイズの引数が異なる場合は、そこに何らかの調整が必要です。

これは、標準のC呼び出し規約を使用する呼び出しプログラムに依存しています。 (varfun()は「add esp、xxx」から引数の数を取得し、(1)短い形式と(2)長い形式の2つの形式のaddがあります。2番目のテストでは、多くの引数をシミュレートして、長い形式を強制します)。

印刷される回答は6と501です。

    varfun(1, 2, 3, 4, 5, 6);
00A03CC8 6A 06                Push        6  
00A03CCA 6A 05                Push        5  
00A03CCC 6A 04                Push        4  
00A03CCE 6A 03                Push        3  
00A03CD0 6A 02                Push        2  
00A03CD2 6A 01                Push        1  
00A03CD4 E8 E5 D3 FF FF       call        _varfun (0A010BEh)  
00A03CD9 83 C4 18             add         esp,18h  
    varfun(1, x);
00A03CDC 81 EC D0 07 00 00    sub         esp,7D0h  
00A03CE2 B9 F4 01 00 00       mov         ecx,1F4h  
00A03CE7 8D B5 28 F8 FF FF    lea         esi,[x]  
00A03CED 8B FC                mov         edi,esp  
00A03CEF F3 A5                rep movs    dword ptr es:[edi],dword ptr [esi]  
00A03CF1 6A 01                Push        1  
00A03CF3 E8 C6 D3 FF FF       call        _varfun (0A010BEh)  
00A03CF8 81 C4 D4 07 00 00    add         esp,7D4h 



#include<stdio.h>
#include<stdarg.h>
void varfun(int i, ...);
int main()
{
    struct eddy
    {
        int x[500];
    } x = { 0 };
    varfun(1, 2, 3, 4, 5, 6);
    varfun(1, x);
    return 0;
}

void varfun(int n_args, ...)
{
    va_list ap;
    unsigned long *p;
    unsigned char *p1;
    unsigned int nargs;
    va_start(ap, n_args);
    p = (long *)(ap - _INTSIZEOF(int) - _INTSIZEOF(&varfun));
    p1 = (char *)*p;
    if (*p1 == 0x83)     // short add sp,x
    {
        nargs = p1[2] / sizeof(int);
    }
    else
    {
        nargs = *(unsigned long *)(p1+2) / sizeof(int);
    }
    printf("%d\n", nargs);
    va_end(ap);
}
3
eddyq

EBPからポインターへのポインターを読み取ります。

#define getReturnAddresses() void ** puEBP = NULL; __asm { mov puEBP, ebp };

使用法

getReturnAddresses();
int argumentCount = *((unsigned char*)puEBP[1] + 2) / sizeof(void*) ;
printf("CalledFrom: 0x%08X Argument Count: %i\n", puEBP[1], argumentCount);

移植性はありませんが、可変数の引数を使用して成功した__cdeclメソッドのx86 C++迂回で使用しました。

スタック/引数に応じて、-1部分を調整する必要がある場合があります。

私はこの方法を思いつきませんでした。 UCフォーラムである時点で見つけたのではないかと疑っています。

適切なコードでこれを使用することはお勧めできませんが、1つの引数で__cdecl呼び出し規約を使用してx86 exeにハッキーな迂回路がある場合、残りは...変数引数で動作する可能性があります。 (Win32)

迂回メソッドの呼び出し規約の例。

void __cdecl hook_ofSomeKind_va_list(void* self, unsigned char first, ...)

証明: 迂回が適用されたターゲットプロセスのx32dbgの横のコンソール出力を示すスクリーンショット

1
Liam Mitchell

末尾にNULLまたはNULLを追加すると、任意の数の引数を使用でき、スタックから出る心配はありません。

#include <cstdarg>
template<typename _Ty>
inline void variadic_fun1(_Ty param1,...)
{
    va_list arg1;
    //TO_DO

    va_end(arg1);
}
template<typename _Ty> 
void variadic_fun2(_Ty param1,...)
{
    va_list arg1;
    va_start(arg1, param1);
    variadic_fun1(param1, arg1, 0);
    va_end(arg1);
}
0
user4853414

このコードでは、ポインタのみを渡すときに可能です

# include <unistd.h>
# include <stdarg.h>
# include <string.h>
# include <errno.h>

size_t __print__(char * str1, ...);
# define print(...) __print__(NULL, __VA_ARGS__, NULL)
# define ENDL "\n"

int main() {

  print("1", ENDL, "2", ENDL, "3", ENDL);

  return 0;
}

size_t __print__(char * str1, ...) {
    va_list args;
    va_start(args, str1);
    size_t out_char = 0;
    char * tmp_str;
    while((tmp_str = va_arg(args, char *)) != NULL)
        out_char = out_char + write(1, tmp_str,strlen(tmp_str));
    va_end(args);
    return out_char;
}
0
Bikash

引数の終わりを示す意味のある値を使用することもできます。0または-1のように、またはushort。に0xFFFFのような最大タイプサイズ

それ以外の場合は、カウントを前もって言及するか、別の引数から控除可能にする必要がありますformat for printf() like functions)

0
CodeAngry