Passing variable arguments to another function that accepts a variable argument list
Asked Answered
B

12

172

So I have 2 functions that both have similar arguments

void example(int a, int b, ...);
void exampleB(int b, ...);

Now example calls exampleB, but how can I pass along the variables in the variable argument list without modifying exampleB (as this is already used elsewhere too).

Berry answered 20/8, 2010 at 12:22 Comment(5)
possible duplicate of Forward an invocation of a variadic function in CMercerize
Well the solution on that one was using vprintf, and that's not the case here.Berry
This is related to, but definitely not the same as, the proposed duplicate: Forward an invocation of a variadic function in C?Waylay
Check this answer: #1516870Tripper
Does this answer your question? Forward an invocation of a variadic function in CReam
W
165

You can't do it directly; you have to create a function that takes a va_list:

#include <stdarg.h>

static void exampleV(int b, va_list args);

void exampleA(int a, int b, ...)    // Renamed for consistency
{
    va_list args;
    do_something(a);                // Use argument a somehow
    va_start(args, b);
    exampleV(b, args);
    va_end(args);
}

void exampleB(int b, ...)
{
    va_list args;
    va_start(args, b);
    exampleV(b, args);
    va_end(args);
}

static void exampleV(int b, va_list args)
{
    ...whatever you planned to have exampleB do...
    ...except it calls neither va_start nor va_end...
}
Waylay answered 20/8, 2010 at 12:28 Comment(4)
Suspected I had to do something like this, problem is the example function is basically a wrapper for vsprintf and not much else :/Berry
@Xeross: Note that this does not change the external specification of what exampleB does - it just changes the internal implementation. I'm not sure what the problem is.Waylay
Is the first parameter required or can all parameters be variadic?Railroader
@Qwerty: The syntax requires a named first argument to a function that takes a variable argument list with , ... in the signature. If you've converted the variable arguments to a va_list, you can pass the va_list to another function that only takes a va_list, but that function (or one that it calls) must have some way of knowing what's in the va_list.Waylay
B
101

Maybe throwing a rock in a pond here, but it seems to work pretty OK with C++11 variadic templates:

#include <stdio.h>

template<typename... Args> void test(const char * f, Args... args) {
  printf(f, args...);
}

int main()
{
  int a = 2;
  test("%s\n", "test");
  test("%s %d %d %p\n", "second test", 2, a, &a);
}

At the very least, it works with g++.

Boyce answered 16/9, 2017 at 13:42 Comment(6)
I'm confused - is this a legitimate approach using C++ >= 11?Homosporous
@DuncanJones Yes, the pack gets expanded by args...Broadcloth
This is a function template, so we end up with many to one mapping, not passing variable arguments between two functions?Kelantan
I cannot believe how hard it was to find an example of how to use this great 10 year old convenience featureNostoc
Thanks. I just used this to abuse the C++ compiler in unfortunate ways: github.com/cubiclesoft/cross-platform-cpp/blob/master/templates/… and github.com/cubiclesoft/cross-platform-cpp/blob/master/templates/…Serrate
How can I get 1 the first element of argArrack
R
17

you should create versions of these functions which take a va_list, and pass those. Look at vprintf as an example:

int vprintf ( const char * format, va_list arg );
Revolver answered 20/8, 2010 at 12:26 Comment(0)
C
9

I also wanted to wrap printf and found a helpful answer here:

How to pass variable number of arguments to printf/sprintf

I was not at all interested in performance (I'm sure this piece of code can be improved in a number of ways, feel free to do so :) ), this is for general debugprinting only so I did this:

//Helper function
std::string osprintf(const char *fmt, ...)
{
    va_list args;
    char buf[1000];
    va_start(args, fmt);
    vsnprintf(buf, sizeof(buf), fmt, args );
    va_end(args);
    return buf;
}

which I then can use like this

Point2d p;

cout << osprintf("Point2d: (%3i, %3i)", p.x, p.y);
instead of for example:
cout << "Point2d: ( " << setw(3) << p.x << ", " << p.y << " )";

The c++ ostreams are beautiful in some aspects, but practically the become horrific if you want to print something like this with some small strings such as parenthesis, colons and commas inserted between the numbers.

Circuitry answered 7/1, 2014 at 19:22 Comment(0)
E
7

A possible way is to use #define:

#define exampleB(int b, ...)  example(0, b, __VA_ARGS__)
Excaudate answered 22/4, 2019 at 10:51 Comment(0)
I
6

It might not be exactly the same situation as described here, but if you were to define a wrapper for a string format function (e.g. logger):

void logger(const char *name, const char *format, ...);
void wrapper(const char *format, ...);

when you implement a wrapper that calls logger, we can just create a string first with vasprintf and then pass it to logger.

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

static void wrapper(const char *format, ...)
{
    char *string;
    va_list args;
    va_start(args, format);

    // variadic printf with allocated string. must free()
    vasprintf(&string, format, args);
    logger("wrapper", "%s", string);

    free(string);
    va_end(args);
}

Not the cleanest, but works. Try this when you must avoid using macro functions.

Immunoreaction answered 22/12, 2021 at 12:54 Comment(2)
Should check the return value of vasprintf.Gangplank
Good catch. It returns -1 when the allocation fails.Immunoreaction
N
3

Incidentally, many C implementations have an internal v?printf variation which IMHO should have been part of the C standard. The exact details vary, but a typical implementation will accept a struct containing a character-output function pointer and information saying what's supposed to happen. This allows printf, sprintf, and fprintf to all use the same 'core' mechanism. For example, vsprintf might be something like:

void s_out(PRINTF_INFO *p_inf, char ch)
{
  (*(p_inf->destptr)++) = ch;
  p_inf->result++;
}

int vsprintf(char *dest, const char *fmt, va_list args)
{
  PRINTF_INFO p_inf;
  p_inf.destptr = dest;
  p_inf.result = 0;
  p_inf.func = s_out;
  core_printf(&p_inf,fmt,args);
}

The core_printf function then calls p_inf->func for each character to be output; the output function can then send the characters to the console, a file, a string, or something else. If one's implementation exposes the core_printf function (and whatever setup mechanism it uses) one can extend it with all sorts of variations.

Nestling answered 20/8, 2010 at 14:34 Comment(0)
W
2

This is the only way to do it.. and the best way to do it too..

static BOOL(__cdecl *OriginalVarArgsFunction)(BYTE variable1, char* format, ...)(0x12345678); //TODO: change address lolz

BOOL __cdecl HookedVarArgsFunction(BYTE variable1, char* format, ...)
{
    BOOL res;

    va_list vl;
    va_start(vl, format);

    // Get variable arguments count from disasm. -2 because of existing 'format', 'variable1'
    uint32_t argCount = *((uint8_t*)_ReturnAddress() + 2) / sizeof(void*) - 2;
    printf("arg count = %d\n", argCount);

    // ((int( __cdecl* )(const char*, ...))&oldCode)(fmt, ...);
    __asm
    {
        mov eax, argCount
        test eax, eax
        je noLoop
        mov edx, vl
        loop1 :
        push dword ptr[edx + eax * 4 - 4]
        sub eax, 1
        jnz loop1
        noLoop :
        push format
        push variable1
        //lea eax, [oldCode] // oldCode - original function pointer
        mov eax, OriginalVarArgsFunction
        call eax
        mov res, eax
        mov eax, argCount
        lea eax, [eax * 4 + 8] //+8 because 2 parameters (format and variable1)
        add esp, eax
    }
    return res;
}
Wirra answered 28/4, 2020 at 7:17 Comment(4)
Why would anyone leave me a down vote this is literally the best way to do this, it pushes all the arguments into the stack using a loop and then all the pushed pushs get called by call eax which is the OriginalVarArgumentsFunction address this way you are allowed to use ... with as much arguments as you please and still able to use it in hooks.Wirra
Have you heard that there are processors which are not i386 ?Boyce
Why on earth would someone hook a CRT function like va_? Also, you realise that if compiled with /MT this is useless? Also, you realise on x64 there is no inline ASM? Also... nah... just not a good answer.Viscoid
There is no way to generate the ... for random variable sized parameters, this is the only way there is no way around this.. I have tested.. you cannot simply call something like this OriginalVarArgsFunction(variable1, format, ...); thats why this fix, fixes the issue thats all Really.Wirra
D
1

Based on the comment that you're wrapping vsprintf, and that this is tagged as C++ I'd suggest not trying to do this, but change up your interface to use C++ iostreams instead. They have advantages over the print line of functions, such as type safety and being able to print items that printf wouldn't be able to handle. Some rework now could save a significant amount of pain in the future.

Deboradeborah answered 20/8, 2010 at 13:34 Comment(2)
To what advantages are you referring?Carnahan
@cjcurrie: the advantage is type safety, even with user-defined types. The C functions cannot handle user-defined types at all, of course.Waylay
H
1

using GNU C extensions:

int FIRST_FUNC(...){
    __builtin_return(
        __builtin_apply(
            (void(*)())SECOND_FUNC, __builtin_apply_args(), 100));
}
Humboldt answered 4/12, 2022 at 21:50 Comment(2)
Not on clang on MacOs, is that form part of the standard?Hinge
@Hinge Oh yeah, sorry there should have been a comma before the three dots, which makes the whole answer wrong since it's not part of the variable.Humboldt
T
1

C++ rest parameters

Maybe throwing a rock in a pond here, but since C++20, you can do this:

void f1(auto...); // a variadic template; “template<class... Ts> void f3(Ts...)”
void f2(auto, ...); // a variadic function; “template<class T> void f3(T...)”

Meaning, instead of

template <typename ...Arguments> void my_printf(Arguments ...arguments) {
    [[maybe_unused]] int return_value{std::printf(arguments...)};
}

you can simply do

void my_printf(auto ...arguments) {
    [[maybe_unused]] int return_value{std::printf(arguments...)};
}

.


To answer your question,

if your declaration is set in stone with nameless rest parameters ..., you cannot access them this way.

Terms answered 25/3, 2024 at 4:48 Comment(0)
L
0

Using the new C++0x standard, you may be able to get this done using variadic templates or even convert that old code to the new template syntax without breaking anything.

Liaoyang answered 20/8, 2010 at 12:56 Comment(1)
unfortunately this is not possible in all instances - just try using lambdasMarbut

© 2022 - 2025 — McMap. All rights reserved.