C语言 如何计算传递给接受可变数量参数的函数的参数数量?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4421681/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 07:22:25  来源:igfitidea点击:

How to count the number of arguments passed to a function that accepts a variable number of arguments?

cvariadic-functions

提问by codeomnitrix

How to count the no of arguments passed to the function in following program:

如何计算以下程序中传递给函数的参数数量:

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

This program's output over my gcc compiler under ubuntu 10.04:

该程序在 ubuntu 10.04 下通过我的 gcc 编译器的输出:

234561345138032514932134513792

so how to find how many no. of arguments actually passed to the function?

那么如何找到多少没有。实际传递给函数的参数?

回答by Alexandre C.

You can't. You have to manage for the caller to indicate the number of arguments somehow. You can:

你不能。您必须设法让调用者以某种方式指示参数的数量。你可以:

  • Pass the number of arguments as the first variable
  • Require the last variable argument to be null, zero or whatever
  • Have the first argument describe what is expected (eg. the printf format string dictates what arguments should follow)
  • 将参数的数量作为第一个变量传递
  • 要求最后一个变量参数为空、零或其他
  • 让第一个参数描述预期的内容(例如 printf 格式字符串指示应遵循哪些参数)

回答by Dan Fabulich

You can let the preprocessor help you cheat using this strategy, stolen and tweaked from another answer:

您可以让预处理器使用此策略帮助您作弊,从另一个答案中窃取和调整:

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

#define PP_NARG(...) \
         PP_NARG_(__VA_ARGS__,PP_RSEQ_N())
#define PP_NARG_(...) \
         PP_128TH_ARG(__VA_ARGS__)
#define PP_128TH_ARG( \
          _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;
}

There's a few clever tricks here.

这里有一些巧妙的技巧。

1) Instead of calling the variadic function directly, you're calling a macro that counts the arguments and passes the argument count as the first argument to the function. The end result of the preprocessor on main looks like:

1) 不是直接调用可变参数函数,而是调用一个宏来计算参数并将参数计数作为第一个参数传递给函数。main 上预处理器的最终结果如下所示:

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

2) PP_NARGis a clever macro to count arguments.

2)PP_NARG是一个聪明的宏来计算参数。

The workhorse here is PP_128TH_ARG. It returns its 128th argument, by ignoring the first 127 arguments (named arbitrarily _1_2_3etc.), naming the 128th argument N, and defining the result of the macro to be N.

这里的主力是PP_128TH_ARG。它通过忽略前 127 个参数(任意命名_1_2_3等),将第 128 个参数命名为N,并将宏的结果定义为 ,从而返回其第 128 个参数N

PP_NARGinvokes PP_128TH_ARGwith __VA_ARGS__concatenated with PP_RSEQ_N, a reversed sequence of numbers counting from 127 down to 0.

PP_NARG调用PP_128TH_ARG__VA_ARGS__连接在一起PP_RSEQ_N,从 127 倒数到 0 的倒序数字序列。

If you provide no arguments, the 128th value of PP_RSEQ_Nis 0. If you pass one argument to PP_NARG, then that argument will be passed to PP_128TH_ARGas _1; _2will be 127, and the 128th argument to PP_128TH_ARGwill be 1. Thus, each argument in __VA_ARGS__bumps PP_RSEQ_Nover by one, leaving the correct answer in the 128th slot.

如果不提供参数,则 的第 128 个值为PP_RSEQ_N0。如果将一个参数传递给PP_NARG,则该参数将传递给PP_128TH_ARGas _1_2将127和第128个参数PP_128TH_ARG为1。因此,在每个参数__VA_ARGS__颠簸PP_RSEQ_N了一个,剩下的128插槽的正确答案。

(Apparently 127 arguments is the maximum C allows.)

(显然127 个参数是 C 允许的最大值。)

回答by The Archetypal Paul

You can't. Something else has to tell you (for instance for printf, it's implied by the number of % format descriptors in the format string)

你不能。还有一些东西要告诉你(例如对于 printf,它由格式字符串中 % 格式描述符的数量暗示)

回答by Jens Gustedt

If you have a C99 compliant compiler (including the preprocessor) you can circumvent this problem by declaring a macro that computes the number of arguments for you. Doing this yourself is a bit tricky, you may use P99_VA_ARGSfrom the P99 macro packageto achieve this.

如果您有一个符合 C99 的编译器(包括预处理器),您可以通过声明一个为您计算参数数量的宏来规避这个问题。这样做你自己是一个小技巧,你可以使用P99_VA_ARGSP99宏包来实现这一目标。

回答by Marcelo Cantos

You can't. varargs aren't designed to make this possible. You need to implement some other mechanism to tell the function how many arguments there are. One common choice is to pass a sentinel argument at the end of the parameter list, e.g.:

你不能。varargs 并非旨在使这成为可能。您需要实现一些其他机制来告诉函数有多少个参数。一种常见的选择是在参数列表的末尾传递一个哨兵参数,例如:

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

Another is to pass the count at the beginning:

另一种是在开始时通过计数:

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

This is cleaner, but not as safe, since it's easier to get the count wrong, or forget to update it, than it is to remember and maintain the sentinel at the end.

这更干净,但不那么安全,因为比在最后记住和维护哨兵更容易弄错计数或忘记更新它。

It's up to you how you do it (consider printf's model, in which the format string determines how many — and what type — of arguments there are).

这取决于你如何做(考虑 printf 的模型,其中格式字符串决定了有多少和什么类型的参数)。

回答by eddyq

The safest way is as described above. But if you REALLY need to know the number of arguments without adding the extra argument mentioned then you can do it this way (but note that it is very machine dependent, OS dependent and even, in rare cases, compiler dependent). I ran this code using Visual Studio 2013 on a 64 bit DELL E6440.

最安全的方法如上所述。但是,如果您真的需要知道参数的数量而不添加提到的额外参数,那么您可以这样做(但请注意,它非常依赖于机器,依赖于操作系统,甚至在极少数情况下依赖于编译器)。我在 64 位 DELL E6440 上使用 Visual Studio 2013 运行此代码。

Another point, at the point where I divided by sizeof(int), that was because all of my arguments were int's. If you have different size arguments, there my need to be some adjustment there.

另一点,在我除以 sizeof(int) 的那一点,那是因为我的所有参数都是 int 的。如果您有不同的大小参数,我需要在那里进行一些调整。

This relies on the calling program to use the standard C calling convention. (varfun() gets the number of arguments from the "add esp,xxx" and there are two forms of the add, (1) short form and (2) long form. In the 2nd test I passed a struct because I wanted to simulate lots of arguments to force the long form).

这依赖于调用程序使用标准的 C 调用约定。(varfun() 从“add esp,xxx”中获取参数的数量,add 有两种形式,(1) 短形式和 (2) 长形式。在第二个测试中,我通过了一个结构体,因为我想模拟大量参数以强制使用长格式)。

The answers printed will be 6 and 501.

打印的答案将是 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);
}

回答by CodeAngry

You could also use a meaningful value that indicates end of arguments. Like a 0 or -1. Or a max type size like 0xFFFF for a ushort.

您还可以使用一个有意义的值来指示参数的结尾。就像 0 或 -1。或者像 0xFFFF 这样的最大类型大小ushort

Otherwise, you need to mention the count upfront or make it deductible from another argument (formatfor printf()like functions).

否则,您需要预先提及计数或使其可从另一个参数中扣除format对于printf()类似函数)

回答by Bikash

In this code it is possible when you pass only pointer

在这段代码中,当您只传递指针时是可能的

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

回答by Liam Mitchell

Read a pointer to pointers from EBP.

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

Not portable, but I have used it in a x86 C++ detour of __cdecl method that took a variable number of arguments to some success.

不可移植,但我在 __cdecl 方法的 x86 C++ 绕道中使用它,该方法采用可变数量的参数取得了一些成功。

You may need to adjust the -1 part depending on your stack/arguments.

您可能需要根据堆栈/参数调整 -1 部分。

I did not come up with this method. Suspect I may have found it on UC forums at some point.

我没有想出这个方法。怀疑我可能在某个时候在 UC 论坛上找到了它。

I can't recommend to use this in propper code, but if you have a hacky detour on a x86 exe with __cdecl calling convention with 1 argument and then the rest are ... variable arguments it might work. (Win32)

我不建议在 propper 代码中使用它,但是如果你在 x86 exe 上有一个 hacky 绕道, __cdecl 调用约定和 1 个参数,然后其余的是 ... 可变参数它可能会工作。(Win32)

Example calling convention of detour method.

绕行方法的调用约定示例。

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

Proof: Screen shot showing console output next to x32dbg on target process with a detour applied

证明: 屏幕截图显示了目标进程上 x32dbg 旁边的控制台输出,并应用了绕路

回答by Liam Mitchell

Appending or NULL at the end makes it possible for me to have any number of arguments and not worry about it going out of the stack

最后追加或 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);
}