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

12

53

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:

234561345138032514932134513792

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

Brickkiln answered 12/12, 2010 at 12:40 Comment(3)
groups.google.com/group/comp.std.c/browse_frm/thread/…Kenishakenison
your program is working all right on my machine. It prints all the arguments passed to the functionConspiracy
related question: Macro to count number of argumentsForeword
C
72

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)
Charleen answered 12/12, 2010 at 12:44 Comment(7)
@codeomnitrix: it's sad but true. As a rule of thumb, stay away from variable length arguments. Unless you do C++0x.Charleen
For now, also stay away from C++0x. However the variadic templates in C++0x are really nice.Oporto
@Matt: With variadic templates you can also write type safe variadic functions.Charleen
Every modern language has the ability to count a list except C++, what a shame.Sheerlegs
@HarounHajem, C++ is not a modern language as programming languages go. :)Johnnyjumpup
It is my understanding that in standard C you cannot use NULL to terminate the argument list of a variadic function without risking undefined behavior so your second bullet point, while it might often work, is a bad choice. You basically must tell the function how many arguments there are, with your first and second bullet points being the only two feasible possibilities. Some compilers however have features that allow a NULL terminator like gcc which has __attribute__((sentinel)) while other C-like language compilers have, for example, NS_REQUIRES_NIL_TERMINATION.Confessor
find a (to me) clearer example for this answer here: cplusplus.com/forum/beginner/284071Strove
N
33

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:

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

2) PP_NARG is a clever macro to count arguments.

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

PP_NARG invokes PP_128TH_ARG with __VA_ARGS__ concatenated with PP_RSEQ_N, a reversed sequence of numbers counting from 127 down to 0.

If you provide no arguments, the 128th value of PP_RSEQ_N is 0. If you pass one argument to PP_NARG, then that argument will be passed to PP_128TH_ARG as _1; _2 will be 127, and the 128th argument to PP_128TH_ARG will be 1. Thus, each argument in __VA_ARGS__ bumps PP_RSEQ_N over by one, leaving the correct answer in the 128th slot.

(Apparently 127 arguments is the maximum C allows.)

Nates answered 29/2, 2016 at 6:18 Comment(1)
I checked your ways. Thank you! See comment to answer for details: #1689442Randazzo
C
8

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)

Commutative answered 12/12, 2010 at 12:44 Comment(1)
Beware that printf("%%") expects no arguments, though.Grindstone
S
8

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_ARGS from the P99 macro package to achieve this.

Sylviesylvite answered 12/12, 2010 at 22:18 Comment(1)
Ok thanks, but much more beyond my c knowledge. I will try to understand this oneBrickkiln
A
4

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.:

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).

Apocalyptic answered 12/12, 2010 at 12:45 Comment(1)
I've been using NULL as a sentinel for a list of pointer arguments and it has worked for me but I've discovered that apparently in general this could result in undefined behavior because of how NULL is implemented in some architectures.Confessor
T
3

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.

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.

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).

The answers printed will be 6 and 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);
}
Teenager answered 12/5, 2015 at 18:58 Comment(0)
L
1

Instead of va_list, using __VA_ARGS__ on array let you achieve the desired result:

#include<stdio.h>

#define print_numbers(...)                      \
do {                                            \
    int parameters[] = {__VA_ARGS__};           \
    int count = sizeof(parameters)/sizeof(int); \
                                                \
    for (int i = 0; i < count; i++)             \
        printf("%d", parameters[i]);            \
} while(0)

int main()
{
    print_numbers(1, 2, 3, 4, 5, 6);
    print_numbers(7);
    print_numbers(8, 9, 10);
}
//12345678910

p.s. use typeof() (which is a gcc/clang extension; now C23 standard) instead of int for more generic usage.

Leptospirosis answered 24/12, 2022 at 10:8 Comment(2)
same as your answer, but to me a bit clear and 'nicer': cplusplus.com/forum/beginner/284071Strove
@PaulEfford Well, that's C++. This question is about C. Or, if you are referring to putting NULL at the end, that only works for sentinel arrays that don't have 0(NULL) as their element.Leptospirosis
W
0

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.

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

Waki answered 14/5, 2014 at 7:17 Comment(0)
C
0

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;
}
Caryloncaryn answered 10/9, 2017 at 3:7 Comment(1)
How do you know that the next pointer is null? Or is it pointing to uninitialised memory?Witless
Z
-1

Read a pointer to pointers from 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.

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

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

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)

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

Zitella answered 18/7, 2018 at 8:31 Comment(0)
V
-1

The simpler, and I think more elegant, way is to use a std::initializer_list. And you can get rid of the extra brackets with a simple macro, see this example - and especially the elts.size() call after the usual for loop:

void doList(const std::initializer_list<std::string>& elts) {
    int count = 0;
    for (std::string s : elts) {
        cout << "(" << count++ << ") " << s << endl;
    }
    cout << "total: " << count << " also as: " << elts.size() <<endl;
}
// and to get rid of extra brackets:
#define DOLIST(...) doList({ __VA_ARGS__ })

Use:

doList({ "this", "is", "a", "initializer_list", "example" } );
DOLIST("and", "a", "variant", "without", "brackets");
Vernacularize answered 17/3, 2021 at 9:12 Comment(2)
whoever put me a negative quote on this answer has possibly not seen that after the traditional for loop that indeed doesn't provide any direct count, the initializer list allows using: elts.size() that perfectly answers the question.Vernacularize
I'm only guessing, but I suspect the downvotes might be related to the fact that the question is tagged c, but this is a C++ answer.Jocularity
S
-1

you may need to add a template and a name to your pack, but this prints 3 in this case.

lista.insertar(0, 4, 'W', 4.7);

template<typename... data>
int Lista::insertar(int pos, data... datos) {
    std::cout<< sizeof...(datos)<<std::endl;
}
Seamanship answered 20/5, 2021 at 19:58 Comment(1)
This C++ solution doesn't answer the OP's C question.Jocularity

© 2022 - 2024 — McMap. All rights reserved.