Как посчитать количество аргументов, переданных функции, которая принимает переменное количество аргументов? - PullRequest
38 голосов
/ 12 декабря 2010

Как посчитать количество аргументов, переданных функции в следующей программе:

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

Вывод этой программы через мой компилятор gcc под Ubuntu 10.04:

234561345138032514932134513792

так как найти сколько нет. аргументов фактически переданных в функцию?

Ответы [ 11 ]

50 голосов
/ 12 декабря 2010

Вы не можете. Вы должны управлять для вызывающей стороны, чтобы указать количество аргументов как-то. Вы можете:

  • Передать количество аргументов в качестве первой переменной
  • Требуется, чтобы последний аргумент переменной был нулевым, нулевым или любым другим
  • Пусть первый аргумент описывает, что ожидается (например, строка формата printf указывает, какие аргументы должны следовать)
13 голосов
/ 29 февраля 2016

Вы можете позволить препроцессору помочь вам обмануть, используя эту стратегию, украденную и подправленную из другого ответа :

#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 вы вызываете макрос, который считает аргументы и передает их в качестве первого аргумента функции.Конечный результат препроцессора на главном выглядит так:

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

2) PP_NARG - это умный макрос для подсчета аргументов.

Рабочая лошадка здесь PP_ARG_N.Он возвращает свой 128-й аргумент, игнорируя первые 127 аргументов (названных произвольно _1 _2 _3 и т. Д.), Называя 128-й аргумент N и определяя результат макроса как N.

PP_NARG вызывает PP_ARG_N с __VA_ARGS__, соединенным с PP_RSEQ_N, обратной последовательностью чисел, начиная с 127 до 0.

Если вы не предоставите аргументы, 128-е значениеPP_RSEQ_N равно 0. Если вы передаете один аргумент PP_NARG, то этот аргумент будет передан PP_ARG_N как _1;_2 будет 127, а 128-й аргумент PP_ARG_N будет 1. Таким образом, каждый аргумент в __VA_ARGS__ удваивает PP_RSEQ_N на единицу, оставляя правильный ответ в 128-м слоте.

(Видимо, 127 аргументов - это максимально допустимое значение C .)

7 голосов
/ 12 декабря 2010

Вы не можете. Что-то еще должно сказать вам (например, для printf это подразумевается числом дескрипторов формата% в строке формата)

5 голосов
/ 13 декабря 2010

Если у вас есть совместимый с C99 компилятор (включая препроцессор), вы можете обойти эту проблему, объявив макрос, который вычисляет количество аргументов для вас.Делать это самостоятельно немного сложно, для этого можно использовать P99_VA_ARGS из макропакета P99 .

4 голосов
/ 12 декабря 2010

Вы не можете. varargs не предназначены, чтобы сделать это возможным. Вам нужно реализовать какой-то другой механизм, чтобы сообщить функции, сколько существует аргументов. Одним из распространенных вариантов является передача аргумента часового в конце списка параметров, например ::100100

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

Другой способ - передать счет в начале:

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

Это чище, но не так безопасно, поскольку легче ошибиться в подсчете или забыть обновить его, чем запоминать и поддерживать дозорного в конце.

Вам решать, как вы это делаете (рассмотрите модель printf, в которой строка формата определяет, сколько и какого типа аргументов существует).

3 голосов
/ 12 мая 2015

Самый безопасный способ, как описано выше. Но если вам ДЕЙСТВИТЕЛЬНО нужно знать количество аргументов без добавления упомянутого дополнительного аргумента, вы можете сделать это следующим образом (но учтите, что это очень зависит от машины, от ОС и даже, в редких случаях, от компилятора). Я запустил этот код с помощью Visual Studio 2013 на 64-битной DELL E6440.

Еще один момент, когда я разделил на sizeof (int), потому что все мои аргументы были int. Если у вас есть аргументы разных размеров, мне нужно внести некоторые коррективы.

Это полагается на вызывающую программу для использования стандартного соглашения о вызове C. (varfun () получает количество аргументов из «add esp, xxx», и есть две формы add, (1) короткая форма и (2) длинная форма. Во 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);
}
1 голос
/ 18 июля 2018

Чтение указателя на указатели из 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);

Не переносимо, но я использовал его в обходе C86 метода __cdecl x86, который принял переменнуюколичество аргументов для некоторого успеха.

Возможно, вам потребуется настроить часть -1 в зависимости от вашего стека / аргументов.

Я не придумал этот метод.Подозреваю, что в какой-то момент я мог найти его на форумах UC.

Я не могу рекомендовать использовать это в коде пропппера, но если у вас есть хакерский обход на exe x86 с соглашением о вызовах __cdecl с 1 параметромтогда остальные ... переменные аргументы, это может работать.(Win32)

Пример соглашения о вызове метода обхода.

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

Подтверждение: Снимок экрана, показывающий вывод консоли рядом с x32dbg в целевом процессе с примененным обходом

0 голосов
/ 05 августа 2018

Добавление или 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 голосов
/ 10 сентября 2017

В этом коде это возможно, когда вы передаете только указатель

# 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 голосов
/ 14 мая 2014

Вы также можете использовать значащее значение, которое указывает конец аргументов. Как 0 или -1. Или максимальный размер шрифта, например 0xFFFF для ushort.

В противном случае вам нужно указать счет заранее или сделать его выводимым из другого аргумента (format для printf() подобных функций) .

...