does varargs offer a kind of poor man's polymorphism?

P

pete

No doubt every other student of C has noticed this; it's new to me.

If I declare:

int xlate( void *, ... );

and then define xlate( ) in several different ways (maybe all
definitions but one are #ifdef-ed out):

int xlate ( char *arg1 ) { ... }
int xlate ( int arg1, char *arg2, int arg3 ) { ... }
int xlate ( char arg1, int *arg2 ) { ... }

and omit any mention of va_list -- never mentioning it -- in every
definition of xlate( ); and then call xlate( ) abiding by one of its
several definitions, it seems that every compiled version of xlate( )
works just the way I want, at least under gcc and msvc.

Is this relaxed, undemanding, generous compiler behavior guaranteed
under C99?

Thanks!

-- Pete
 
S

Seebs

Is this relaxed, undemanding, generous compiler behavior guaranteed
under C99?

No, and there's been many counterexamples on real-world architectures.

Don't do that.

-s
 
P

pete

No, and there's been many counterexamples on real-world architectures.

Don't do that.

-s

Peter, if it's convenient, would you please be good enough to point me
to just any random one of these many counterexamples?

Thanks so much!

-- p
 
E

Eric Sosman

No doubt every other student of C has noticed this; it's new to me.

If I declare:

int xlate( void *, ... );

and then define xlate( ) in several different ways (maybe all
definitions but one are #ifdef-ed out):

int xlate ( char *arg1 ) { ... }
int xlate ( int arg1, char *arg2, int arg3 ) { ... }
int xlate ( char arg1, int *arg2 ) { ... }

Each of the three would have to be in a separate compilation unit,
and at least two would have to be `static', and none of the three
compilation units could "see" the declaration. In other words,

1) In a single compilation unit, a name can be defined at most
once.

2) In a single program, a name with external linkage can refer
to only one function, hence one function definition.

3) In a single compilation unit, all declarations and definitions
of a function must agree.
and omit any mention of va_list -- never mentioning it -- in every
definition of xlate( ); and then call xlate( ) abiding by one of its
several definitions, it seems that every compiled version of xlate( )
works just the way I want, at least under gcc and msvc.

... whatever "works just the way I want" may mean.
Is this relaxed, undemanding, generous compiler behavior guaranteed
under C99?

You haven't described "works just the way I want," so the question
is not well-formed. Even so, I'll venture an answer: NO. I'll also
give the C90 answer: NO. And I'll make a guess at the C1x answer: NO.
What part of "NO" are you having trouble understanding? (And, out of
curiosity, why do you use "polymorphism" to describe what most people
term either "overloading" or "Haddocks' Eyes?")
 
L

luser- -droog

 (And, out of
curiosity, why do you use "polymorphism" to describe what most people
term either "overloading" or "Haddocks' Eyes?")

Yeah. You can't do polymorphism in C (or function dispatch based on
types) without some non-standard extension like gs's typeof,
or maybe with user-defined tag-unions.

BTW, what's "Haddocks' Eyes"? It sounds so tantalizingly mysterious!
 
E

Eric Sosman

Yeah. You can't do polymorphism in C (or function dispatch based on
types) without some non-standard extension like gs's typeof,
or maybe with user-defined tag-unions.

The former is non-standard, but the latter need not be.
BTW, what's "Haddocks' Eyes"? It sounds so tantalizingly mysterious!

GIYF.
 
E

Eric Sosman

[...]
You can pass any type through a var-args call, but get the arguments with va_arg
and don't lie about the type.

Note that va_arg() must specify the *promoted* type: `double' and
`int' instead of `float' and `short', for example. This makes trouble
with types whose promotion rules differ from one system to another.
For example, in

enum TriplePlay { TINKER, EVERS, CHANCE } infielder = CHANCE;
void func(const char *, ...);
func ("What is the type of the second parameter?", infielder);

The type of the second *argument* is `enum TriplePlay', which we know
is "compatible with char, a signed integer type, or an unsigned integer
type," at the implementation's discretion. The compatible type the
implementation chooses may (or may not be) subject to promotion, and if
promotable may promote to pretty much anything. So, what type should
the va_arg() macro use to retrieve the second parameter? Different
implementations will give different answers (and `enum TriplePlay' will
not be among them, if that is a promotable type).

Even `size_t' and `ptrdiff_t' are theoretically subject to this
problem, although in practice they are usually wide enough that they
do not promote. Still, it is possible for a Standard-conforming C to
have `SIZE_MAX < INT_MAX', and if this happens a `size_t' argument
will promote to an `int' parameter and `va_arg(ap, size_t)' will be
incorrect.

For ultimate safety in passing (possibly) promotable types to
varargs functions, I guess you could wrap the suspect value in a
struct or union, types that are never promoted:

union Wrapper { enum TriplePlay payload; } carrier;
carrier.payload = infielder;
func ("The second parameter is a `union Wrapper'.", carrier);

Another possibility is to pass a pointer:

func ("The second parameter points to an `enum TriplePlay'.",
&infielder);

I confess, though, that I've never seen these dodges used -- not even
by people who've previously fallen afoul of an unexpected promotion!
 
S

Seebs

Peter, if it's convenient, would you please be good enough to point me
to just any random one of these many counterexamples?

Don't remember which systems, but there are systems where variadic
functions have all arguments passed on the stack, but other functions
typically use registers for at least some arguments, such as floating
point values.

-s
 
D

David Thompson

[...]
You can pass any type through a var-args call, but get the arguments with va_arg
and don't lie about the type.

Note that va_arg() must specify the *promoted* type: `double' and
`int' instead of `float' and `short', for example. This makes trouble
with types whose promotion rules differ from one system to another.
For example, [enum which may be (compatible with) any integer type]
at the implementation's discretion. The compatible type the
implementation chooses may (or may not be) subject to promotion, and if
promotable may promote to pretty much anything. So, what type should

In principle, but enums in C can never exceed the range of 'int',
which is also normatively (though ill-definedly) 'the natural size'
for the execution environment, so I see no good reason ever to use
anything wider. An implementation can reasonably narrower integers,
but they are all taken to int by the default promotions. Have you seen
actual problems here?
the va_arg() macro use to retrieve the second parameter? Different
implementations will give different answers (and `enum TriplePlay' will
not be among them, if that is a promotable type).

Even `size_t' and `ptrdiff_t' are theoretically subject to this
problem, although in practice they are usually wide enough that they
do not promote. Still, it is possible for a Standard-conforming C to
have `SIZE_MAX < INT_MAX', and if this happens a `size_t' argument
will promote to an `int' parameter and `va_arg(ap, size_t)' will be
incorrect.
Yeah, that one is usually okay but could be a problem.

Similarly but without vararg, getting socklen_t to port right has
wasted more of my time than can be justified in any sane universe.

<snip rest>
 
M

Malcolm McLean

        func ("The second parameter points to an `enum TriplePlay'.",
              &infielder);

I confess, though, that I've never seen these dodges used -- not even
by people who've previously fallen afoul of an unexpected promotion!
In high-level languages you often see this

answer = power(base = x, exponent = y)

it means that caller can miss out parameters, and doesn't have to
remember the order in which arguments are passed. It also gives a
powerful clue to the reader what they mean.
You can do the same with C varags, but I must confess I've never see
it done.
 
M

Mark Wooding

Malcolm McLean said:
In high-level languages you often see this

answer = power(base = x, exponent = y)

You can do the same with C varags, but I must confess I've never see
it done.

The X Toolkit Intrinsics (Xt) library does something rather like this,
e.g., in XtVaCreateWidget. The Gobject system also has something
similar, in g_object_new and friends.

-- [mdw]
 
S

Stephen Sprunk

No doubt every other student of C has noticed this; it's new to me.

If I declare:

int xlate( void *, ... );

and then define xlate( ) in several different ways (maybe all
definitions but one are #ifdef-ed out):

int xlate ( char *arg1 ) { ... }
int xlate ( int arg1, char *arg2, int arg3 ) { ... }
int xlate ( char arg1, int *arg2 ) { ... }

and omit any mention of va_list -- never mentioning it -- in every
definition of xlate( ); and then call xlate( ) abiding by one of its
several definitions, it seems that every compiled version of xlate( )
works just the way I want, at least under gcc and msvc.

Is this relaxed, undemanding, generous compiler behavior guaranteed
under C99?

No; this invokes undefined behavior. One possible result of undefined
behavior may be doing exactly what you want--until it comes time to port
to another implementation or demo your "working" code to your boss/customer.

There exist implementations which pass arguments to variadic functions
on the stack but arguments to non-variadic functions in registers, which
is allowed by the Standard but will obviously break your code.

S
 

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

No members online now.

Forum statistics

Threads
473,776
Messages
2,569,603
Members
45,189
Latest member
CryptoTaxSoftware

Latest Threads

Top