web-dev-qa-db-fra.com

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

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

234561345138032514932134513792

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

39
codeomnitrix

Tu ne peux pas. Vous devez gérer pour que l'appelant indique en quelque sorte le nombre d'arguments. Vous pouvez:

  • Passez le nombre d'arguments comme première variable
  • Exiger que le dernier argument de 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 les arguments à suivre)
51
Alexandre C.

Vous pouvez laisser le préprocesseur vous aider à tricher en utilisant cette stratégie, volée et modifiée depuis ne autre réponse :

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

Il y a quelques astuces intelligentes ici.

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

_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 renvoie son 128e argument, en ignorant les 127 premiers arguments (nommés arbitrairement _1_2_3 etc.), en nommant le 128e argument N et en définissant le résultat de la macro comme N.

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

Si vous ne fournissez aucun argument, la 128e valeur de PP_RSEQ_N est 0. Si vous passez un argument à PP_NARG, alors cet argument sera passé à PP_ARG_N comme _1; _2 sera 127, et le 128e argument de PP_ARG_N sera égal à 1. Ainsi, chaque argument de __VA_ARGS__ bosses PP_RSEQ_N plus d'un, laissant la bonne réponse dans le 128ème emplacement.

(Apparemment 127 arguments est le maximum autorisé par C .)

14
Dan Fabulich

Tu ne peux pas. Quelque chose d'autre doit vous dire (par exemple pour printf, cela est impliqué par le nombre de descripteurs de format% dans la chaîne de format)

7
The Archetypal Paul

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 cela vous-même est un peu délicat, vous pouvez utiliser P99_VA_ARGS à partir du package de macros P99 pour y parvenir.

5
Jens Gustedt

Tu ne peux pas. varargs n'est pas conçu 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 sentinelle à la fin de la liste des paramètres, par exemple:

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

Une autre consiste à passer le dé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 et de maintenir la sentinelle à la fin.

C'est à vous de voir comment vous le faites (considérez le modèle de printf, dans lequel la chaîne de format détermine combien - et quel type - d'arguments il y a).

4
Marcelo Cantos

Le moyen le plus sûr est celui décrit ci-dessus. Mais si vous avez VRAIMENT besoin de connaître le nombre d'arguments sans ajouter l'argument supplémentaire mentionné, vous pouvez le faire de cette façon (mais notez qu'il dépend beaucoup 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ù j'ai divisé par sizeof (int), c'était parce que tous mes arguments étaient des int. Si vous avez des arguments de taille différente, il faut que je fasse quelques ajustements.

Cela dépend du programme appelant pour utiliser la convention d'appel C standard. (varfun () obtient le nombre d'arguments de "add esp, xxx" et il y a deux formes de l'add, (1) forme courte et (2) forme longue. Dans le 2ème test, j'ai réussi une structure parce que je voulais simuler de nombreux 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<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

Lisez un pointeur vers des pointeurs depuis 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 C86 x86 de la méthode __cdecl qui a pris un nombre variable d'arguments pour réussir.

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

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

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

Exemple 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é

1
Liam Mitchell

Ajouter ou NULL à la fin me permet d'avoir un certain nombre d'arguments et de ne pas avoir peur qu'il sorte de la pile

#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

Dans ce code, il est possible lorsque vous passez uniquement le pointeur

# 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

Vous pouvez également utiliser une valeur significative qui indique la fin des arguments. Comme un 0 ou -1. Ou une taille de type maximale comme 0xFFFF pour un ushort.

Sinon, vous devez mentionner le nombre à l'avance ou le déduire d'un autre argument (format pour printf() comme des fonctions) .

0
CodeAngry