va_list passed to a function using va_arg is not working
Asked Answered
A

3

0

I've this code that plays with variadic functions:

#include <cstdarg>
#include <iostream>

template <typename T>
void bar(va_list vl) {
    std::cout << va_arg(vl, T) << std::endl;
}

void foo(int n, ...) {
    va_list vl;
    va_start(vl, n);
    while (n--) {
        bar<int>(vl);
    }
    va_end(vl);
}

int main() {
    foo(3, 1, 2, 3);
    return 0;
}

Unfortunately, the output of this code is platform dependent. In GCC 7 I get:

1
2
3

while in MSVC 2015 is

1
1
1

I'm tring to write code to get the GCC output, but I suppose I'm doing something wrong and I've no idea where. Which is the correct output, if any?

Both compilers are set at maximum warning level, but they are not printing any warning. In MSVC, being va_list an alias to char*, the output makes sense. In GCC it is a builtin type and I've no idea what happens there.

Edit:

If I change the definition of bar to void bar(va_list& vl) then the outputs are the same. Is it legal to pass a va_list by reference?

Arni answered 3/2, 2020 at 14:37 Comment(1)
[Pro Tip] Don't use old c-style variadic functions. Prefer C++'s variadic templates instead.Waine
F
4

Your program invokes undefined behavior.

The semantics of C standard library in C++ is the same as in C and the content of cstdarg should be the same as stdarg.h in C. C++ standard is big, so I'll use C standard here, which is easier for me to read. From C99 7.15.3, annotation mine:

.... The object ap [of type va_list, my annotation] may be passed as an argument to another function; if that function invokes the va_arg macro with parameter ap, the value of ap in the calling function is indeterminate and shall be passed to the va_end macro prior to any further reference to ap.

If you pass va_list object to another function and in that function you call va_arg, you have to call va_end in the same function.

According to the footnote 221 from C99 you can pass a pointer to va_list.

Felonious answered 3/2, 2020 at 14:45 Comment(3)
Is it legal to pass va_list by reference? It seems to work, too.Arni
This is a good question. If it's legal, I don't know. Sure, the semantics of using reference vs, like, dereferenced pointer should be the same, so it should work. Note that the parameter left of ... can't be a reference, from here.Felonious
@GiovanniCerretani You could use a pointer instead of reference. That's allowed as per C standard.Weekend
C
0

You should use modern C++11 approach. Using old fashion C variadic function is to dangerous.

You could use C++ template parameter pack, but in your case simple std::initializer_list just do the job and it is quite handy:

void foo(std::initializer_list<int> items) {
    for (auto item : items) {
        std::cout << item  << std::endl;
    }
}

int main() {
    foo({1, 2, 3});

    return 0;
}

demo

Celenacelene answered 3/2, 2020 at 15:10 Comment(0)
E
0

The issue with the original code (which will happen only on some platforms, depending on the particular implementation of va_list) is that when va_arg is performed on vl here:

template <typename T>
void bar(va_list vl) {
    std::cout << va_arg(vl, T) << std::endl;
}

the changes are not reflected in 'vl' here:

void foo(int n, ...) {
    va_list vl;
    va_start(vl, n);
    while (n--) {
        bar<int>(vl);
    }
    va_end(vl);
}

There are even more issues on some platforms with passing va_list as pointer (C/C++) or reference(for C++).

The workaround that works well is to wrap va_list into a struct:

/** @brief the cross-platform way to pass va_list back and forth in functions */
struct va_list_container {
    va_list args;
};

and pass the pointer to that struct to bar.

I've got the following result with the original code on Macos:

make 1
c++     1.cpp   -o 1
Workspace % ./1
1
1
1

but when I made the following changes:

Workspace % cat 1.cpp
#include <cstdarg>
#include <iostream>

struct va_list_container {
    va_list args;
};

template <typename T>
void bar(struct va_list_container *p_vlc) {
    std::cout << va_arg(p_vlc->args, T) << std::endl;
}

void foo(int n, ...) {
    struct va_list_container vlc;
    va_start(vlc.args, n);
    while (n--) {
        bar<int>(&vlc);
    }
    va_end(vlc.args);
}

int main() {
    foo(3, 1, 2, 3);
    return 0;
}

Workspace % make 1
c++     1.cpp   -o 1
Workspace % ./1
1
2
3

PS: The option with reference which also has the same output:

#include <cstdarg>
#include <iostream>

struct va_list_container {
    va_list args;
};

template <typename T>
void bar(struct va_list_container & vlc) {
    std::cout << va_arg(vlc.args, T) << std::endl;
}

void foo(int n, ...) {
    struct va_list_container vlc;
    va_start(vlc.args, n);
    while (n--) {
        bar<int>(vlc);
    }
    va_end(vlc.args);
}

int main() {
    foo(3, 1, 2, 3);
    return 0;
}
Edan answered 26/7, 2024 at 4:5 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.