次のプログラムで関数に渡された引数の数を数える方法:
#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
どれだけの数を見つけるか関数に実際に渡される引数の数?
できません。なんらかの方法で引数の数を示すために、呼び出し元を管理する必要があります。あなたはできる:
この戦略を使用して、 別の回答 から盗まれて微調整されたチートをプリプロセッサに任せることができます。
#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が許す最大値です 。)
できません。他の何かがあなたに伝えなければなりません(たとえば、printfの場合、フォーマット文字列内のフォーマット記述子の数によって暗示されます)
C99準拠のコンパイラ(プリプロセッサを含む)がある場合、引数の数を計算するマクロを宣言することでこの問題を回避できます。これを自分で行うのは少し注意が必要です。P99_VA_ARGS
から P99マクロパッケージ これを達成するために。
できません。可変引数は、これを可能にするようには設計されていません。他のメカニズムを実装して、引数の数を関数に伝える必要があります。一般的な選択肢の1つは、パラメーターリストの最後にセンチネル引数を渡すことです。例:
varfun(1, 2, 3, 4, 5, 6, -1);
もう1つは、最初にカウントを渡すことです。
varfun(6, 1, 2, 3, 4, 5, 6);
これはクリーンですが、最後にセンチネルを覚えて維持するよりも、カウントを間違えたり、更新を忘れたりする方が簡単なので、安全ではありません。
どのように行うかはあなた次第です(printfのモデルでは、フォーマット文字列が引数の数(およびタイプ)を決定します)。
最も安全な方法は上記のとおりです。ただし、前述の余分な引数を追加せずに引数の数を本当に知る必要がある場合は、この方法で行うことができます(ただし、非常にマシン依存、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);
}
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, ...)
末尾に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);
}
このコードでは、ポインタのみを渡すときに可能です
# 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または-1のように、またはushort
。に0xFFFFのような最大タイプサイズ
それ以外の場合は、カウントを前もって言及するか、別の引数から控除可能にする必要があります(format
for printf()
like functions)。