There is no general way for a function to determine the number and type of the optional arguments it was called with. So whoever designs the function typically designs a convention for the caller to specify the number and type of arguments. It is up to you to define an appropriate calling convention for each variadic function, and write all calls accordingly.
One kind of calling convention is to pass the number of optional arguments as one of the fixed arguments. This convention works provided all of the optional arguments are of the same type.
A similar alternative is to have one of the required arguments be a bit mask, with a bit for each possible purpose for which an optional argument might be supplied. You would test the bits in a predefined sequence; if the bit is set, fetch the value of the next argument, otherwise use a default value.
A required argument can be used as a pattern to specify both the number
and types of the optional arguments. The format string argument to
printf is one example of this (see Formatted Output Functions).
Another possibility is to pass an “end marker” value as the last
optional argument. For example, for a function that manipulates an
arbitrary number of pointer arguments, a null pointer might indicate the
end of the argument list. (This assumes that a null pointer isn't
otherwise meaningful to the function.) The
execl function works
in just this way; see Executing a File.