6

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

Carl Norum
  • 219,201
  • 40
  • 422
  • 469
Pete Wilson
  • 8,610
  • 6
  • 39
  • 51

5 Answers5

4

No, it's more a poor man's overloading. Polymorphism (the ability to perform an action on multiple object types and have each do its correct thing) in C is usually done with structures containing function pointers.

And you can't just blindly use as many arguments as you like. Either you have a fixed minimum number of arguments which can inform the function how many variable ones to expect, or you you have a sentinel argument at the end to indicate you're done.

In other words, something like:

printf ("%d: %s\n", int1, charpointer2);
x = sum_positive_values (1, 2, 3, 4, 5, -1);
Community
  • 1
  • 1
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • Hmmm. Fixed min number of args? That doesn't feel right to me, given the little I know about variadic functions. But, yes, there must be at least one arg. Fixed maximum num, maybe. See, I'm worried about whether the ret out of the function will leave the stack in good shape -- your comment bears on that concern. – Pete Wilson Dec 17 '10 at 02:25
  • I meant fixed minimum as in `printf` (and fixed minimum includes "one" but there may be more such as in `[fs]printf`) - you need the fixed args to tell you how many more args they're are, whether that be a count or a string with `%` markers. – paxdiablo Dec 17 '10 at 02:29
  • Yes, I agree "one" is a number :-). But, see, I observe experimentally that I can indeed call a func with any number of args and and the func will behave (and return) correctly no matter how many args it's expecting. Just for fun, try your printf example, slightly modified: printf( "%d: %s %s %s %s %s %s %s %s %s %s %s %s %s\n", int1, charpointer2); It works great! – Pete Wilson Dec 17 '10 at 02:55
  • 1
    No, don't do that. If it works, it's totally accidental. It's usually okay to provide more arguments than `%` markers but not so good the other way around. – paxdiablo Dec 17 '10 at 03:06
  • Overloading is a kind of polymorphism mechanism (at source code level, not binary). – Vovanium Dec 17 '10 at 11:49
3

If you declare xlat as taking void *, you can't just go and implement it with int. Even poor-man's overloading is to be done properly, and would maybe look like

enum {
        T_FOO,
        T_BAR,
};

void xlat(enum tp type, ...)
{
        struct foo *foo;
        struct bar *bar;
        va_list argp;
        va_start(argp, type);
        if (type == T_FOO) {
                foo = va_arg(argp, struct foo *);
                do_something_with_foo;
        } else if (type == T_BAR) {
                bar = va_arg(argp, struct bar *);
                do_something_with_bar;
        }
}

Though I guess that's more like overloading than polymorphism.

user502515
  • 4,346
  • 24
  • 20
  • Well, because void* can take any value, I imagined that the receiving function (i.e., xlate) can understand/use/interpret any arg any way it wants. That's the whole point, actually. Thanks for your comments. I am hoping to hear that C99 promises such behavior. Yes, it's overloading. – Pete Wilson Dec 17 '10 at 02:16
3

No, such behaviour is not guaranteed by the standard. The relevant text is in §6.5.2.2:

9 If the function is defined with a type that is not compatible with the type (of the expression) pointed to by the expression that denotes the called function, the behavior is undefined.

Some platforms need to use a different calling convention when calling varargs functions, because their usual calling convention requires the callee to know how many actual arguments were passed. The C standard was written with this specifically in mind - so varargs functions can only be called through a correctly typed varargs declaration, and expressions denoting varargs functions can only be used to call varargs functions.

You can do what you want by creating matching declarations of each function, wrapped in the same #ifdef magic that selects one that is also used to select the correct function definition.

caf
  • 233,326
  • 40
  • 323
  • 462
  • Sorry, I must be dense, but I do not see what paragraph 9 has to do with function arguments. As I read the paragraph, it tells me that if I call a function expecting it to be of one type (int, say) and it's in reality of another type (char *, for example), then when I call that function "the behavior" is undefined. Makes perfect sense. But what does the paragraph say about function arguments? How am I misreading paragraph 9, please? Thanks! – Pete Wilson Dec 17 '10 at 03:52
  • 1
    @Pete Wilson: The "type" of the function includes its return type *and* its arguments. Eg the type of the `strcmp()` function is `int (const char *, const char *)` – caf Dec 17 '10 at 03:57
  • Sigh. You know much better than that. The blob of stuff you mention might be the "signature" of strcmp(), but you well know it is not the "type" of strcmp() any more than the content of a variable, or the variable's length in bits, say anything about its "type". A function's "type" is exactly the type of the value it returns. Period. The "type" of strcmp() is int. Period. The notion of a function's "type" as we are speaking of it has been around and understood since the first days of Fortran in the 1950s if not before. Again, this is no news to you but I can't let stand what you said. – Pete Wilson Dec 17 '10 at 04:59
  • 2
    @Pete Wilson: In the context of C, a function type does indeed include its parameter list: §6.2.5p20 says: *"A function type is characterized by its return type and the number and types of its parameters."*. In C, `int` is an object type, not a function type. The type of an expression that applies the function call `()` operator to the `strcmp()` function is indeed `int`, but the *type of the function itself* is not. Sigh indeed! – caf Dec 17 '10 at 05:14
3

In addition to caf's answer:

This can't work because of several problems, and the standard couldn't do anything else than to forbid such a thing:

Prototypes tell the calling side how argument conversions have to be performed when calling the function. Already the example you give would not work reliably for the first parameter. You declare it void* and then int in another. Since both may have different width, your code is condemned to fail on most 64 bit architectures.

Even worse, the ... notation tells the calling side to apply default promotions for the remaining arguments. E.g if your implementation would expect a float the calling side would always provide a double and, again, your code would crash badly (= lately).

Then, modern architectures have complicated rules which type of arguments they put on the stack and which are kept in registers. This depends on the type of the argument, e.g integers and floating points have different sets of registers. So this will get your arguments completely wrong.

Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177
2

It's not the compiler that makes this funny-business work so much as it is the ABI of the platform for which you're compiling. A variadic function call is made using the same rules as any other function call, so it stands to reason that if you pass the right number and type of arguments, even if the calling function thinks it's variadic, the receiving function will be able to understand the arguments correctly (even if the receiving function isn't variadic).

Carl Norum
  • 219,201
  • 40
  • 422
  • 469
  • Yes, that's right. Well put: the caller thinks the function is variadic, but the function cares nothing about such naive assumptions. And yes it stands to reason. – Pete Wilson Dec 17 '10 at 02:37
  • 1
    A variadic function call is *not* necessarily made using the same rules as any other function call - particularly on platforms where the callee is expected to pop the function arguments from the stack. The C standard goes to some trouble to allow implementers that latitude. – caf Dec 17 '10 at 02:57