altering an av_list variable

P

Punkie

When having optional function arguments, the extra arguments are accessed
via an av_list structure. While traversing is trivial, changing the value
isnt.

<code>
void my_sprintf (char *dest, size_t size, const char *format, ...)
{
<snip>
va_list argptr;
va_start(argptr, format);
while((pos = strcspn(format,"%")) != max) {
switch(format[pos+1]) {
case 'm':
format[pos+1] = 's';
argval = va_arg(cpy,int);
/* change the argval to a char* */
break;
default:
va_arg(cpy,int);
}
}
sprintf(*dest, const char *format, argptr); /* call the std function */
va_end(argptr);
}
</code>

In this example you see an attempt to make the sprintf function also
recognise the "%m" format. It should retrieve the (int) argument and change
its value.

Is it possible to alter the arguments in the va_list at all? Are there other
tricks to do this without rewriting sprintf completely?
 
T

Tor Rustad

Punkie wrote:

[...]
Are there other tricks to do this without rewriting sprintf completely?

See

http://www.ijs.si/software/snprintf/


A quick and non-portable way, is getting the length via:

null = fopen("/dev/null", "w"); /* UNIX/Linux*/
null = fopen("NUL", "w"); /* Windows */

and then calling

len = vfprintf(null, format, ap);


Note, vfprintf() return int, not size_t.
 
P

Punkie

Indeed, a good way to do this without rewriting sprintf is to let *others*
rewrite it. However something i didnt find among them is typechecking.

Any sprintf with the same std formatting can use the attributes to defer
typechecking too. Like:
void my_sprintf (char *dest, size_t size, const char *format, ...)
__attribute__((format(printf, 3, 4)));

Altering the types accepted creates a problem as we cant use the attribute
anymore.
2routes to solve this:
*own sprintf implementation, that also does this checking somehow. hmmm but
how?
*filter the format and va_list. Like in the code I presented earlier,
changing the type or eliminaing the arg from the va_list.

Tor said:
Punkie wrote:

[...]
Are there other tricks to do this without rewriting sprintf completely?

See

http://www.ijs.si/software/snprintf/


A quick and non-portable way, is getting the length via:

null = fopen("/dev/null", "w"); /* UNIX/Linux*/
null = fopen("NUL", "w"); /* Windows */

and then calling

len = vfprintf(null, format, ap);


Note, vfprintf() return int, not size_t.
 
K

Kenny McCormack

When having optional function arguments, the extra arguments are accessed
via an av_list structure. While traversing is trivial, changing the value
isnt.

"av_list" and "va_list" are very different things. The people here will
not talk to you about the former.
 
I

Ian Collins

Punkie wrote:

Please don't top-post.
Tor said:
Punkie wrote:

[...]
Are there other tricks to do this without rewriting sprintf completely?
See

http://www.ijs.si/software/snprintf/


A quick and non-portable way, is getting the length via:

null = fopen("/dev/null", "w"); /* UNIX/Linux*/
null = fopen("NUL", "w"); /* Windows */

and then calling

len = vfprintf(null, format, ap);


Note, vfprintf() return int, not size_t.
Indeed, a good way to do this without rewriting sprintf is to let *others*
rewrite it. However something i didnt find among them is typechecking.

Any sprintf with the same std formatting can use the attributes to defer
typechecking too. Like:
void my_sprintf (char *dest, size_t size, const char *format, ...)
__attribute__((format(printf, 3, 4)));
It should be noted that __attribute__ is a (very handy) gcc extension.
 
C

Chris Torek

Indeed, a good way to do this without rewriting sprintf is
to let *others* rewrite it.
Indeed.

Altering the types accepted creates a problem as we cant
use the [gcc-specific] attribute anymore.

You cannot use this portably anyway, even if all your format
directives match those for printf, since __attribute__ is a
gcc-specific extension (i.e., it does not work very well, or even
at all, in various other C compilers).

If you have "extra" directives, a la syslog() "%m", you have to
work harder. If your extra directives need to access some of
the va_list's variable arguments, you must in fact "rewrite sprintf".

Consider, for instance, the following actual implementation (for
V9 SPARC, with a fairly old version of GCC):

/*__va_ll must be 8 bytes regardless of -mlong32; this avoids a pedwarn()*/
typedef long __va_ll __attribute__((__mode__(__DI__)));

/*
* Pre-V9, we simply had up to 6 values in %o registers, then the rest
* in memory. The V9 convention is different -- it had to change because
* the registers are wider, and someone took the opportunity to fix the
* brokenness: floating point arguments are passed in via the first 16
* %f registers now. Thus, we now have an "argument descriptor" data
* structure.
*
* (Using "long long" for the ni and nd produces shorter code.)
*/
typedef struct __va_data {
__va_ll __va_ni; /* # integers remaining */
__va_ll *__va_ip; /* integer-reg arguments */
__va_ll __va_nd; /* # doubles remaining */
double *__va_dp; /* floating-reg arguments */
__va_ll *__va_rest; /* additional args, if any */
} va_list[1];

/*
* The __builtin_args_info expressions return the number of "registers"
* (if any) worth of fixed arguments. For integer parameters, this is
* simply the number of (widened) integers; for float, double, and long
* double parameters, it is the number of %f registers used, accounting
* for the fact that "double" parameters are passed in an even/odd pair
* (leaving a gap if necessary -- e.g., "void f(float x,double y,...)"
* uses %f0 and <%f2:%f3> for a total of 4 %f registers).
*
* When we invoke __builtin_saveregs() here, it dumps those registers
* that were *not* used up by fixed parameters, integer registers first,
* then %f registers. The latter always starts with an even register
* (to form a pair), even if there were an odd number of fixed "float"
* arguments.
*/
#define va_start(ap, l) __extension__ ({ \
__va_ll *__va_base = __builtin_saveregs(); \
int __va_nfi = __builtin_args_info(0); \
int __va_nff = __builtin_args_info(1); \
if (__va_nfi > 6) \
__va_nfi = 6; \
if (__va_nff > 16) \
__va_nff = 16; \
(ap)->__va_ip = __va_base; \
(ap)->__va_dp = (double *)(__va_base + __va_nfi) ; \
(ap)->__va_ni = 6 - __va_nfi; \
(ap)->__va_nd = 8 - ((__va_nff + 1) / 2); \
(ap)->__va_rest = (__va_ll *)__builtin_next_arg(l); \
})
#define va_end(ap)

/*
* Locate (get the address of) the next integer register. Used for
* integers and pointers (in particular, for the hidden pointer that
* is used to pass aggregates).
*/
#define __va_aireg(ap) \
((ap)->__va_ni <= 0 ? (ap)->__va_rest++ : \
((ap)->__va_ni--, (ap)->__va_ip++))

/*
* Internally, this macro sets __va_p to point to the first byte of the
* object, whatever it is, then extracts the appropriate object.
*
* "Real" parameters are the hardest, because we may need to skip over
* a %f-pair-gap when extracting a long double. Fortunately, the number
* of doubles remaining is odd iff the corresponding parameter skipped
* a %f register pair.
*
* Integer and aggregate parameters are easy, but since we are locating
* the bytes of the integer in question, we need to account for our
* big-endian offset: an int or unsigned int is in the last four, not
* the first four, bytes of the 8-byte integer register value as stored
* in memory.
*/
#define va_arg(ap, ty) __extension__ ({ \
void *__va_p; \
const int __vaclass = __builtin_classify_type(*(ty *)0); \
if (__vaclass >= __record_type_class) \
__va_p = *(void **)__va_aireg(ap); \
else if (__vaclass == __real_type_class) { \
const int __va_n = (sizeof(*(ty *)0) + 7) / 8; \
if (__va_n > 1 && (ap)->__va_nd & 1) \
(ap)->__va_nd--, (ap)->__va_dp++; \
if (((ap)->__va_nd -= __va_n) >= 0) { \
__va_p = (ap)->__va_dp; \
(ap)->__va_dp += __va_n; \
} else { \
__va_p = (ap)->__va_rest; \
(ap)->__va_rest += __va_n; \
} \
} else { \
/* oddly, we get better code with two statements here */ \
__va_p = __va_aireg(ap); \
__va_p = (char *)__va_p + 8 - sizeof(*(ty *)0); \
} \
*(ty *)__va_p; \
})

Here, substituting or removing a parameter is pretty tricky,
especially if the substitute has a different size and/or type from
the original (e.g., replacing a 4-byte "int" with an 8-byte pointer,
or pointer with floating-point value). You need to know which of
the three regions the parameter to be substituted or removed falls
into, and for substitutions, whether the replacement goes in the
same region or not. This information has been removed (from the
structure to which "ap" points) by the time you have used va_arg()
to extract the original value.

Another implementation is much simpler:

#define va_arg(ap, type) \
((type *)(ap += sizeof(type)))[-1]

(where va_start(ap, last) just sets "ap" to point to the right
place in the [single] stack that this implementation uses). Here
substitutions or deletions are usually just a matter of poking the
right value into place, or doing a memmove(). Finding the right
location is easy; finding the size for memmove() is harder but
not nearly as hard as the V9 SPARC version.

Nothing requires the implementation of va_start, va_arg, and so on
to look like one of the two above, but they are the two common
methods I have seen. (Most or all of the tricky stuff can be, and
in my opinion should be, done internally by the compiler, allowing
the following compiler-specific -- but not machine-specific --
variant of <stdarg.h>:

#define va_start(ap, last) __builtin_va_start(ap)
#define va_arg(ap, ty) __builtin_va_arg(ap, ty)
#define va_end(ap) __builtin_va_end(ap)

For some reason, not too many people seem to agree with me on
this one. :) )
 

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,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top