Title: c variadic functions confusion
Tags: c;function;macros;ellipsis;variadic
Question: I'm trying to figure out what's behind va_start(), va_arg() macroses. The code below works well.
#include <iostream&gt;
#include <cstdarg&gt;
void f(double a, double b, ...)
va_list arg;
va_start(arg, b);
double d;
while((d = va_arg(arg, double)) != 0)
{
st75b5:f2c1:f5d7:b954:8dc3:cee9:baf:a8c0cout << d << '\n';
}
int main(int argc, char *argv[])
f(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 0.0);
return 0;
As I was expected it gave such output: 3 4 5 6 7 8 9.
Then I found definitions of that macroses (in internet, cause my header stdarg.h is mysterious -- it defines macros va_arg(v, l) like _builtin_va_arg(v, l), the last doesn't defined in it and stdarg.h doesn't include anything, so is it in some library??? ). Nevertheless, in place of "cstdarg" I wrote:
typedef char* va_list;
#define _INTSIZEOF(n) \
((sizeof(n)+sizeof(int)-1) &amp;~(sizeof(int)-1))
#define va_start(ap,v) \
(ap = (va_list)&amp;v + _INTSIZEOF(v))
#define va_arg(ap,t) \
(*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
#define va_end(ap) (ap = (va_list)0)
The output became wierd, such as 1
-0.0409377
-0.0409377
4.88084e-270
4.85706e-270
I thought that variadic parameters are placed just next to last declared parameter, but apparently there is more intricated situation. I'd be very pleased if somebody unveil where am I wrong or what's really happens there.
Comment: I didn't mention it before, but handwritten macros works fine with int type, that is all parameters are placed as expected relatively to last defined argument. In case of double type parameters are also in stack, but in odd manner, namely I could obtain correct result adding initial six to pointer to last argument. Is it some kind of padding? More over, I found that defined arguments are there twice. You can see it placing start pointer to first argument. You'll get them, then four stuff numbers and then again all parameters one by one.

Comment: I do understand that it's far away from utility to know it, but still curious. Thanks.


Here is another answer: Your handwritten macros would work on a machine that always passes all arguments on the stack and pads up smaller types to sizeof(int).  However, many machines (most these days?) don't pass arguments on the stack -- they instead pass them in registers and only use the stack if there are too many to fit in registers.
So in order to deal with va_args, the compiler needs to know the ABI and which arguments will be placed where in what circumstances.  What is generally done is to have a va_list contain a number of arrays (enough to hold all the registers that might contain args) and a number of pointers (generally, one for each type of register and one for the stack.  va_start dumps all the argument registers into the arrays and initializes the pointers, and va_arg then figures out what kind of register the given argument type would be passed in and pulls the value out of appropriate spot.  So for a hypothetical processor with 8 regs for integer/pointer args and 8 regs for float/double args, you might have something like:
typedef struct {
intptr_t iregs[8], *iptr;
double   fregs[8], *fptr;
char     *spptr;
} va_list;
inline void _builtin_va_start(va_list &amp;ap, arg) {
// dump the registers might be used to pass args into ap-&gt;iregs and ap-fregs,
// setup iptr and fptr to point into iregs and fregs after the arguments that
// correspond to 'arg' and those before it.  spptr points into the stack space
// used for arguments after regs run out
inline _builtin_va_arg(va_list &amp;ap, type) {
if (type is integer or pointer) {
if (ap-&gt;iptr == ap-&gt;iregs+8) {
rv = *(type *)ap-&gt;spptr;
ap-&gt;spptr += sizeof(type);
} else {
rv = *ap-&gt;iptr++;
}
} else if (type is float or double) {
if (ap-&gt;fptr == ap-&gt;fregs+8) {
rv = *(type *)ap-&gt;spptr;
ap-&gt;spptr += sizeof(type);
} else {
rv = *ap-&gt;fptr++;
}
} else {
// some other type (struct?) deal with it
Notice that neither of these _builtin_va functions can be written in C; they need to be built in to the compiler

Here is another answer: The va_start, va_arg and don't forget va_end are specific for a compiler. You can not just take them from somewhere else and expect them to work. You'd better follow the manual page in using them and only try to understand their inner workings if you are a compiler engineer.
P.S: Oh, and their definitions are usually very mysterious using subtle tricks to get it working.
P.S2: In answer to your question where _builtin_va_arg is defined: it is known by the compiler, a so called builtin. You will find it in the sources of the compiler ;)
Comment for this answer: Yeah, I can't find it indeed. Thank you for hint to look at it this way.
Comment for this answer: To be clear, you probably won't find a *function* named `_builtin_va_arg` in the sources of the compiler. What you'll find is code that recognized that implementation-defined keyword and does the appropriate thing with it. It's built into the compiler in much the same way that the `+` and `sizeof` operators are.

{'source': 'StackExchangeDataset', 'subset': 'stackoverflow.com', 'metadata': {'AnswerCount': 2, 'CommentCount': 2, 'ContentLicense': 'CC BY-SA 3.0', 'CreationDate': '2013:00:03/29/13', 'FavoriteCount': 1.0, 'PostTypeId': 1, 'Tags': '<c><function><macros><ellipsis><variadic>', 'Title': 'c variadic functions confusion', 'ViewCount': 450, 'id': 15712760, 'user_AboutMe': None, 'user_AccountId': 2099414.0, 'user_DisplayName': 'Nephew', 'user_Reputation': 21.0, 'user_Views': 10.0, 'user_id': 2225756.0}}