Cătălina Sîrbu
Cătălina Sîrbu

Reputation: 1283

Calling a function with va_list argument needs va_start() at the beggining?

A function with the following header:

int max(int n, va_list vals)

Called inside the function:

int max_first(int n, ...)

Needs a va_start(vals, n) invocation at the body's beginning? I tried without and it works but I don't understand which is the correct way of doing it.

int max(int n, va_list vals)
{
    va_start(vals, n);
    // etc
}

Upvotes: 3

Views: 2650

Answers (3)

The question is if

int max(int n, va_list vals)

Called inside the function:

int max_first(int n, ...)
 

Needs a va_start(vals, n) invocation at the body's beginning?

No, it does not and it must not, the correct pattern is as follows:

int max_first(int n, ...) {
    va_list vals;
    va_start(vals, n);
    int rv = max(n, vals);
    va_end(vals);
    return rv;
}

then

int max(int n, va_list vals) {
    for (int i = 0; i < n; i ++) {
        int val = va_arg(vals, int);
        ...
    }

    ...
}

i.e. you can call va_start only in a function that has ... and you need to pass in the argument immediately preceding the ..., and each call to va_start must be always followed by va_end for the same value, and if you pass it to a function then you must call va_end right afterwards without using it in the calling function; if you want to process the arguments again, you must then call va_start again.

Upvotes: 6

MrHappyAsthma
MrHappyAsthma

Reputation: 6522

Let's start with some background: per the documentation, you need to call va_start before invoking va_arg on any va_list:

  • "va_start should be invoked with an instance to a valid va_list object ap before any calls to va_arg". (Source)

  • "Prior to calling va_arg, ap must be initialized by a call to either va_start or va_copy, with no intervening call to va_end". (Source)

I would imagine not calling va_start is undefined behavior, but I couldn't find a specific callout to state as such.

In your question, the function int max(int n, va_list vals) is not "really" variadic, since it accepts a fixed number of arguments: 2. These are int n and va_list vals.

Per the documentation "The declaration of a variadic function uses an ellipsis as the last parameter, e.g. int printf(const char* format, ...);"

So, it depends on how you implement it, but I would recommend documenting the int max(int n, va_list vals) to accept a va_list that is already initialized with a va_start call. The rationale being that it is not technically "variadic" and does not really "own" the va_list. It just accepts it as an input from some other source.

The actual variadic function int max_first(int n, ...) should be the one creating the va_list and priming it with the call to va_start before passing it anywhere.

Although, as far as I can tell, there's no way to check if a va_list already has va_start called on it or not. And there's no guarantee that it will be called before being passed to your function, so I suspect this would have to be enforced by documentation and convention.

Upvotes: 3

user694733
user694733

Reputation: 16043

You must initialize va_list with va_start in max_first. But you must not redo it in max, because that function doesn't have necessary callframe information.

Upvotes: 4

Related Questions