Comment compter le nombre d’arguments passés à une fonction qui accepte un nombre variable d’arguments?

Comment compter le nombre d’arguments passés à la fonction dans le programme suivant:

#include #include 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); } 

La sortie de ce programme sur mon compilateur gcc sous Ubuntu 10.04:

 234561345138032514932134513792 

alors comment trouver combien de non. des arguments réellement passés à la fonction?

Tu ne peux pas. Vous devez gérer pour que l’appelant indique le nombre d’arguments d’une manière ou d’une autre. Vous pouvez:

  • Passer le nombre d’arguments comme première variable
  • Exiger que le dernier argument de la variable soit nul, nul ou autre
  • Demandez au premier argument de décrire ce qui est attendu (par exemple, la chaîne de format printf dicte quels arguments doivent suivre)

Vous pouvez laisser le préprocesseur vous aider à sortingcher en utilisant cette stratégie, volée et modifiée d’ une autre réponse :

 #include  #include  #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; } 

Il y a quelques astuces intelligentes ici.

1) Au lieu d'appeler directement la fonction variadic, vous appelez une macro qui compte les arguments et passe le nombre d'arguments comme premier argument à la fonction. Le résultat final du préprocesseur sur main ressemble à ceci:

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

2) PP_NARG est une macro intelligente pour compter les arguments.

Le cheval de bataille ici est PP_ARG_N . Il retourne son 128ème argument en ignorant les 127 premiers arguments (nommés arbitrairement _1 _2 _3 etc.), en nommant le 128ème argument N et en définissant le résultat de la macro sur N

PP_NARG appelle PP_ARG_N avec __VA_ARGS__ concaténé avec PP_RSEQ_N , une séquence inversée de nombres allant de 127 à 0.

Si vous ne fournissez aucun argument, la PP_RSEQ_N valeur de PP_RSEQ_N est 0. Si vous transmettez un argument à PP_NARG , cet argument est transmis à PP_ARG_N sous la forme _1 ; _2 sera 127 et le PP_ARG_N argument de PP_ARG_N sera 1. Ainsi, chaque argument de __VA_ARGS__ PP_RSEQ_N de un, laissant la bonne réponse à la 128ème position.

(Apparemment, 127 arguments correspondent au maximum autorisé par C. )

Tu ne peux pas. Quelque chose d’autre doit vous dire (par exemple, pour printf, c’est implicite par le nombre de descripteurs de format% dans la chaîne de formatage)

Si vous disposez d’un compilateur compatible C99 (y compris le préprocesseur), vous pouvez contourner ce problème en déclarant une macro qui calcule le nombre d’arguments pour vous. Faire ceci vous-même est un peu délicat, vous pouvez utiliser P99_VA_ARGS du paquet de macros P99 pour y parvenir.

Tu ne peux pas. Les varargs ne sont pas conçus pour rendre cela possible. Vous devez implémenter un autre mécanisme pour indiquer à la fonction le nombre d’arguments. Un choix courant consiste à passer un argument sentinel à la fin de la liste de parameters, par exemple:

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

Une autre consiste à passer le compte au début:

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

C’est plus propre, mais pas aussi sûr, car il est plus facile de se tromper ou d’oublier de le mettre à jour que de se souvenir de la sentinelle et de la maintenir à la fin.

C’est à vous de décider comment vous allez le faire (considérez le modèle de printf, dans lequel la chaîne de formatage détermine combien – et quel type – d’arguments il y a).

Le moyen le plus sûr est comme décrit ci-dessus. Mais si vous devez VRAIMENT connaître le nombre d’arguments sans append l’argument supplémentaire mentionné, vous pouvez le faire de cette façon (mais notez qu’il est très dépendant de la machine, du système d’exploitation et même, dans de rares cas, du compilateur). J’ai exécuté ce code à l’aide de Visual Studio 2013 sur un DELL E6440 64 bits.

Un autre point, au point où je me suis divisé par sizeof (int), c’est que tous mes arguments étaient des int. Si vous avez des arguments de taille différente, il est nécessaire de procéder à quelques ajustements.

Cela repose sur le programme appelant à utiliser la convention d’appel C standard. (varfun () obtient le nombre d’arguments de “add esp, xxx” et il existe deux formes de add, une (1) forme abrégée et (2) une forme longue. Dans le deuxième test, j’ai passé un struct parce que je voulais simuler beaucoup d’arguments pour forcer la forme longue).

Les réponses imprimées seront 6 et 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 #include 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); } 

Vous pouvez également utiliser une valeur explicite indiquant la fin des arguments. Comme un 0 ou -1. Ou une taille de type maximale telle que 0xFFFF pour un ushort .

Sinon, vous devez mentionner le nombre dès le départ ou le déduire d’un autre argument ( format pour les fonctions printf() ) .

Dans ce code, il est possible de ne passer que le pointeur

 # include  # include  # include  # include  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; } 

Lire un pointeur sur des pointeurs d’EBP.

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

Usage

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

Pas portable, mais je l’ai utilisé dans un détour x86 C ++ de la méthode __cdecl qui prenait un nombre variable d’arguments avec un certain succès.

Vous devrez peut-être ajuster la partie -1 en fonction de votre stack / arguments.

Je n’ai pas trouvé cette méthode. Je pense que je l’ai peut-être trouvé sur des forums UC à un moment donné.

Je ne peux pas recommander d’utiliser ceci dans le code propper, mais si vous avez un détour par un hacky sur un fichier exe x86 avec la convention d’appel __cdecl avec 1 argument, le rest sont … des arguments variables, cela pourrait fonctionner. (Win32)

Exemple de convention d’appel de la méthode de détour.

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

Preuve: Capture d’écran montrant la sortie de la console à côté de x32dbg sur le processus cible avec un détour appliqué

Ajouter ou NULL à la fin me permet d’avoir un nombre quelconque d’arguments et de ne pas craindre que cela sorte de la stack.

 #include  template inline void variadic_fun1(_Ty param1,...) { va_list arg1; //TO_DO va_end(arg1); } template void variadic_fun2(_Ty param1,...) { va_list arg1; va_start(arg1, param1); variadic_fun1(param1, arg1, 0); va_end(arg1); } 

C’est possible et c’est simple, il suffit d’append une autre variable k dans la boucle et de l’affecter initialement 1, essayez ce code

 #include  #include  void varfun(int i, ...); int main(){ varfun(1,2); return 0; } void varfun(int n_args, ...) { va_list ap; int i, t, k; k = 1; va_start(ap, n_args); for(i=0;i <= va_arg(ap, int);i++){ k+=1; } printf("%d",k); va_end(ap); }