Safe to get address of va_list function parameter?

S

skillzero

Is it safe to get the address of a va_list parameter to a function,
later dereference it to pass to va_arg, and have the original va_list
variable updated correctly? The case I'm worried about is when va_list
is an array type. Taking the address of a local variable va_list
that's an array type will be the same as the passing the name, but
taking the address of an array type that's a function parameter will
give the address of the function parameter pointer value. For example:

void MyFunction1( const char *format, va_list args )
{
MyFunctionCore( format, &args );
}

void MyFunctionCore( const char *format, va_list *args )
{
MyFunctionCore1( format, args ); // Pulls 2 int's off the va_list (1
and 2)
MyFunctionCore2( format, args ); // Pulls the next 2 int's off the
va_list (3 and 4)
}

void MyFunctionCore1( const char *format, va_list *args )
{
int x, y;

x = va_arg( *args, int );
y = va_arg( *args, int );
}

void MyFunctionCore2( const char *format, va_list *args )
{
int a, b;

a = va_arg( *args, int );
b = va_arg( *args, int );
}

I also want it to work for the case when the va_list is a local
variable:

void MyFunction2( const char *format, ... )
{
va_list args;

va_start( args, format );
MyFunctionCore( format, &args );
va_end( args );
}
 
P

Peter Nilsson

Is it safe to get the address of a va_list parameter to
a function, later dereference it to pass to va_arg, and
have the original va_list variable updated correctly?

Yes, N1256 even includes a footnote saying this is valid.
The case I'm worried about is when va_list
is an array type.

Properly coded, va_list's type is incidental.
 
K

Kaz Kylheku

Is it safe to get the address of a va_list parameter to a function,
later dereference it to pass to va_arg, and have the original va_list
variable updated correctly? The case I'm worried about is when va_list
is an array type.

If va_list is an array type, it means that parameters of that type are
actually pointers, but other local variables are of that array type.

If va_list is an array type, it also means you can't return a va_list
from a function.

The va_* macros can be constructed so that they don't care.
It's an unlikely implementation, but not impossible.

Only you can decide the level of portability you want in your code.

va_list is tricky. There are restrictions. A va_start done on a va_list must be
matched by a va_end, and it has to be in the same function. If you have two
copies of a va_list obtained by regular assignment or parameter passing, and
va_arg is invoked on one of them, the other copy becomes indeterminate.

So I can see why you want to do what you are trying to do: pass around
a single va_list so there are no invalid copies.

In order to make that potable to arrays, you will have to wrap it in
a structure. Parameters of that structure type will behave consistently
with local variables, and you can return that structure:

struct va { va_list v; };

void bottom(const char *, struct va *);

void top(const char *fmt, ...)
{
struct va va;

va_start (va.v, fmt);
bottom(fmt, &va);
va_end (va.v, fmt);

}

void bottom(const char *fmt, struct va *pva)
{
int x;
/* ... */
x = va_arg(pva->v, int);
/* ... */
}

Another idea is to adopt a coding convention such that you avoid
taking the address of a va_list which is a parameter:

void bottom(const char *, va_list *);

void topv(const char *fmt, va_list vl)
{
/* va_list may be an array, so vl above may be a pointer. */
/* Solution: make a copy from param to variable. */

va_list vl_copy;

va_copy (vl_copy, vl);
bottom(fmt, &vl_copy);
va_end (vl_copy); /* required */
}

void bottom(const char *fmt, va_list *pva)
{
/* no problem */
}

So if va_list is an array, then vl isn't, but vl_copy /is/. And the address of
vl_copy is a pointer-to-array, and so is the second parameter of bottom.

The va_copy macro handles the transfer of value. It has to work even
though one argument is an array and the other a pointer. That's the
implementors' business; they chose that representation and they have
to make the macros work.
 
J

jacob navia

Is it safe to get the address of a va_list parameter to a function,
later dereference it to pass to va_arg, and have the original va_list
variable updated correctly? The case I'm worried about is when va_list
is an array type. Taking the address of a local variable va_list
that's an array type will be the same as the passing the name, but
taking the address of an array type that's a function parameter will
give the address of the function parameter pointer value. For example:

void MyFunction1( const char *format, va_list args )
{
MyFunctionCore( format, &args );
}

void MyFunctionCore( const char *format, va_list *args )
{
MyFunctionCore1( format, args ); // Pulls 2 int's off the va_list (1
and 2)
MyFunctionCore2( format, args ); // Pulls the next 2 int's off the
va_list (3 and 4)
}

void MyFunctionCore1( const char *format, va_list *args )
{
int x, y;

x = va_arg( *args, int );
y = va_arg( *args, int );
}

void MyFunctionCore2( const char *format, va_list *args )
{
int a, b;

a = va_arg( *args, int );
b = va_arg( *args, int );
}

I also want it to work for the case when the va_list is a local
variable:

void MyFunction2( const char *format, ... )
{
va_list args;

va_start( args, format );
MyFunctionCore( format, &args );
va_end( args );
}

This will work under lcc-win 32 bits, but will not work for
lcc-win 64 bits, either under windows or unix. (x86)

The problem is that each access to the va_list needs an inlined function
call to the extractor since it is no longer a linear list.

The data is separated between
(1) integer and pointer data that is stored in the integer registers
(2) floating point data (double, float) that is stored in the xmm
register set
(3) long double data that is stored in memory
(4) aggregates data (structures passed by value) that is stored in
the stack.

If you make

va-arg(args,int);

the compiler will extract the data from the integer registers part.

If you write
va_arg(args,double);
it will extract the data from the floating point register set

etc.

So you can't pass the address of the va_args data. You have to pass
the va_args data structure itself.
 
K

Kaz Kylheku

This will work under lcc-win 32 bits, but will not work for
lcc-win 64 bits, either under windows or unix. (x86)

The problem is that each access to the va_list needs an inlined function
call to the extractor since it is no longer a linear list.

The data is separated between
(1) integer and pointer data that is stored in the integer registers
(2) floating point data (double, float) that is stored in the xmm
register set
(3) long double data that is stored in memory
(4) aggregates data (structures passed by value) that is stored in
the stack.

If you make

va-arg(args,int);

the compiler will extract the data from the integer registers part.

If you write
va_arg(args,double);
it will extract the data from the floating point register set

etc.

So you can't pass the address of the va_args data.

That may be true, but it doesn't logically follow.

Just because va_args contains a mixture of various contents doesn't
mean that it cannot be indirected upon.
You have to pass
the va_args data structure itself.

If it doesn't work, it's broken. There is a footnote in C99:

``It is permitted to create a pointer to a va_list and pass that pointer to
another function, in which case the original function may make further use of
the original list after the other function returns.''

The footnote isn't normative, but it doesn't matter, because it's not the
footnote which is making it well-defined. The footnote is just a reminder
to the implementor not to forget about that case.
 
J

jacob navia

Kaz said:
The footnote isn't normative,

Yes. It isn't.

va_args is a STRUCTURE, not a table. So if you
want to keep it at some state you should just copy
the structure with va_copy
 
B

Ben Bacarisse

jacob navia said:
Yes. It isn't.

va_args is a STRUCTURE, not a table. So if you
want to keep it at some state you should just copy
the structure with va_copy

Is there some significance to the capital letters? I don't see how
its being a structure has any bearing on whether it is legal to pass a
pointer to it. It does not need to be a structure in the C sense, of
course. All that is mandated is that va_list is an object type.
 
J

James Kuyper

jacob said:
Yes. It isn't.

The footnote correctly states something that can be derived from the
normative text, but isn't necessarily obvious - that's the purpose of
footnotes. In this case, the fact that a pointer to a va_list can be
passed around can be derived from the fact that the standard says
nothing to suggest that it is any less feasible to create and use a
pointer to a va_list than it is to create a pointer to any other object
type.
va_args is a STRUCTURE, not a table.

The standard makes no such requirement.
... So if you
want to keep it at some state you should just copy
the structure with va_copy

I believe that the whole point of using pointers in this fashion to to
avoid keeping it in a fixed state: it's state is supposed to be updated
by the functions that the pointer is passed to.
 
D

D Herring

Keith said:
Is it safe to get the address of a va_list function parameter?

Seems sketchy. Varargs are implemented via macros. va_arg() has
destructive semantics, and it might only working in the current stack
frame. What is the address of a register? See `man stdarg`,
especially the section on va_copy().

Since varargs only deal with POD types, why not just copy the value?

- Daniel
 
M

Mick Tully

My name is James Kuyper. Give me your best :)
I'll stick the best suggestion in my sig.

Hi James, welcome to a.a.

Thanksfor the J... Not ;-)

Pause my jerk.

Sorry!

Mick.
 
J

jacob navia

D said:
Seems sketchy. Varargs are implemented via macros. va_arg() has
destructive semantics, and it might only working in the current stack
frame. What is the address of a register? See `man stdarg`, especially
the section on va_copy().

Since varargs only deal with POD types, why not just copy the value?

- Daniel

What I was trying to explain is that in lcc-win64/lcc-unix64 the
va_list is this structure:

struct __va_list {
unsigned long long gp_offset;
unsigned long long fp_offset;
void *overflow_arg_area;
void *reg_save_area;
};

Now, as you see there are 3 areas. Integer data, floating point data,
and overflow (spill) area.

When you take an integer, the gp_offset is incremented by sizeof(int).
When you take a floating point element, the fp_offset member is
incremented.

It is useless to take the address of this structure since the pointers
will be incremented anyway. The only thing to do is to use the va_copy
interface, that will copy the structure by making a fresh COPY, not
a shallow copy by just taking the address.

I am sure my implementation is conforming
 
M

Mick Tully

My name is James Kuyper. Give me your best :)
I'll stick the best suggestion in my sig.

Welcometo a.a James,
Thanks for the J... NOT! ;-)


Pause my jerk.

Sorry!
Cheers,
Mick.
 
J

jameskuyper

D said:
Seems sketchy. Varargs are implemented via macros. va_arg() has
destructive semantics, and it might only working in the current stack
frame. What is the address of a register? See `man stdarg`,
especially the section on va_copy().

The footnote already referenced elsewhere in this thread makes it
clear that it was intended for va_list objects to be usable even when
they refer to arguments of a different function than the one currently
executing. Handling the issues you bring up is the responsibility of
va_start(); it's supposed to put the va_list object into a state that
will allow it to work properly despite those issues.
Since varargs only deal with POD types, why not just copy the value?

POD types is a C++ concept; this message is on comp.lang.c, where all
types are what C++ calls POD types.

The reason using a pointer to the va_list object, rather than a copy
of it, would presumably be because the user wants to make sure that
va_arg() calls performed in the subroutine cause the va_list object in
the calling routine to move forward through the argument list. If that
weren't desired, then va_copy() would have been more appropriate.
 
R

RoLo

Is it safe to get the address of a va_list function parameter?

Keith

I guess your referring to the arguments variable.

define safe.?.?

something like:

var b;
function a()
{
b=arguments;
};

a(0,2);

alert(b);

since the variable b has the pointer stored the garbage collector
shouldn't clean it... so it should not give a problem.

Rolo
 
R

RoLo

I guess your referring to the arguments variable.

define safe.?.?

something like:

var b;
function a()
{
  b=arguments;

};

a(0,2);

alert(b);

since the variable b has the pointer stored the garbage collector
shouldn't clean it... so it should not give a problem.

Rolo

ignore my reply..

weird google groups, I swear I clicked on the javascript
newsgroup... :-S
 
K

Kaz Kylheku

Yes. It isn't.

However, the footnote is not required for that use to be valid.
va_args is a STRUCTURE, not a table. So if you

You can take the address of a structure, pass the pointer somewhere
and then manipulate the structure in that context.

Nothing in the description of va_list prohibits this, or, equivalently,
permits an implementation not to make it work.
want to keep it at some state you should just copy
the structure with va_copy

But the restrictions with respect to the /use/ of va_list do
not have anything to do with manipulating it through a pointer.

The values of va_list objects can in fact be propagated by assignment a
function argument passing. The caveat is that if you extract from the list with
va_arg, then only that copy passed to va_arg is correctly updated (which is why
va_arg has to be a macro). Any other copies become indeterminate, because they
have not been updated with the new value.

If there is only one copy, manipulated by pointer, then this stale copy problem
does not occur.
 
K

Kaz Kylheku

The footnote correctly states something that can be derived from the
normative text, but isn't necessarily obvious - that's the purpose of

And obviously, the footnode hasn't made it obvious enough for everyone.

Maybe important footnotes need to be written in all caps.
I believe that the whole point of using pointers in this fashion to to
avoid keeping it in a fixed state: it's state is supposed to be updated
by the functions that the pointer is passed to.

If there is only one variable with N pointers aliasing to it, then they all see
the same state. After va_arg(*q, ...), any pointer p that is equal to q has a
*p value that is the same as *q (in fact the same object) an so if *q has
a valid state, then so does *p.

I don't see the difficulty.
 
K

Kaz Kylheku

Seems sketchy. Varargs are implemented via macros. va_arg() has
destructive semantics, and it might only working in the current stack
frame.

I don't see anything in the standard about ``might only work with the
current stack frame''. The va_list values can be passed into other
functions by value. For instance, there are exmaples of this in the
standard I/O library: functions that take a va_list, like vprintf.

If you can only use va_list in the original stack frame, then these would
break---unless passing a va_list to a function does something magic to bless
that value for the new stack frame, going beyond the ordinary pass-by-value
semantic of C.
What is the address of a register? See `man stdarg`,

In backticks? Shell command substitution is off topic in comp.lang.c,
and the modern POSIX syntax is $(man stdarg) anyway. :) :)

Unix man pages haven't defined C since the mid 1980's. You can use man pages to
study historic C. See, for instance, whether you system has:

man vararg
man bcopy

Et cetera.
especially the section on va_copy().

va_copy is quite irrelevant. The point of va_copy is to clone an
independent va_list object which does not become indeterminate when the
original value is mutated with va_arg, and which maintains its own
independent position in within the argument list, not affected by
invoking va_arg on other copies.

The situation in question is one in which there is exactly one va_list
object the entire time, passed around by pointer. There are no copies
of that object which can become stale, or which are wrongly expected to
maintain some original position in the va_list.
 
K

Kaz Kylheku

What I was trying to explain is that in lcc-win64/lcc-unix64 the
va_list is this structure:

struct __va_list {
unsigned long long gp_offset;
unsigned long long fp_offset;
void *overflow_arg_area;
void *reg_save_area;
};

Okay. So va_list is ``typedef struct __va_list va_list;''.
Now, as you see there are 3 areas. Integer data, floating point data,
and overflow (spill) area.

Yes. So this is an object type. It has some reference semantics inherent in it.

Like suppose that the program makes a copy of a va list like this:

va_list vl, vl_copy;

va_start(vl, fmt);

vl_copy = vl;

Obviously, this is a shallow copy, right? So then if va_arg is called on the
original, it will do things like manipulate gp_offset and who knows what else.
These changes won't be reflected in the original object, which shares some of
the data by reference, and now has a stale state.
When you take an integer, the gp_offset is incremented by sizeof(int).
When you take a floating point element, the fp_offset member is
incremented.

That's perfectly fine. So the state of a va_list changes when va_arg is
applied. That's exactly what the interface is designed for.
It is useless to take the address of this structure since the pointers
will be incremented anyway.

At this point, I'm starting to disbelieve your claim that it does not
work. Did you try it?

It's not at all useless to take the address of this structure and pass it into
a helper function. The helper function can then call va_arg(*ptr, ...). The
gp_offset or fp_offset members are then nicely incremented in that structure.
The one and only copy of that structure.
The only thing to do is to use the va_copy
interface, that will copy the structure by making a fresh COPY, not
a shallow copy by just taking the address.

The address of an object is not a shallow copy. The address is the object
itself, not a copy at all! (If it's a copy of anything, it's just a copy of its
address).

A shallow copy occurs when you assign the structure, or pass it as a parameter,
without doing anything with the contents---the corresponding members of the
structure are just copied to each other as if by assignment.

Yet, this is exactly what you recommended in your earlier post, as the right
alternative instead of passing a pointer.

Passing va_list by value, without using va_copy, not only has to work, but is a
common technique. Functions can be defined which accept a va_list parameter,
and are allowed to use va_arg to extract from it.

That's fine because it's accompanied by a restriction: if va_arg is used on the
shallow copy, then the original becomes garbage; it cannot be used for va_arg
extraction.

Moreover, as far cleanup is concerned, va_end may only be called on a va_list
that was created in the same function. So that simplifies any resource
management concerns.
I am sure my implementation is conforming

But did you actually confirm that it breaks?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,900
Latest member
Nell636132

Latest Threads

Top