1

I am currently changing the function signatures of a class of functions in an application. These functions are being stored in a function table, so I was expecting to change this function table as well. I have just realised that in certain instances, we already use the new function signature. But because everything is casted to the correct function type as it is put into the function table, no warnings are being raised.

When the function is called, it will be passed extra parameters that are not really part of the function declaration, but they are on the end of the parameter list.

I can't determine if this is guaranteed by the way function parameters are passed in C. I guess to do variadic functions like sprintf, it has to be the case that earlier arguments can be resolved correctly whatever is on the end of the parameter list?

It evidently works just fine across multiple platforms but out of curiosity I'd like to know how and why it works.

asc99c
  • 3,815
  • 3
  • 31
  • 54
  • It's going to depend on your calling convention. For cdecl it may not matter, but for something like stdcall, you will run into problems. – Richard J. Ross III May 10 '13 at 12:52
  • Can you show what is the function pointer declarations in the table and the actual declaration of the function you call? – ouah May 10 '13 at 12:52

2 Answers2

3

But because everything is casted to the correct function type as it is put into the function table, no warnings are being raised.

So the compiler gets to be no help to speak of. C programmers cast too much. >_<

I can't determine if this is guaranteed by the way function parameters are passed in C. I guess to do variadic functions like sprintf, it has to be the case that earlier arguments can be resolved correctly whatever is on the end of the parameter list?

Technically, you've got undefined behavior. But it's defined for your platform to use the standard C calling conventions (see Scott's answer), or something that maps directly to them (usually by mapping the first N parameters to a certain set of processor registers).

This comes up a lot with variable argument lists, too. For example, printf is declared something like:

int printf(const char* format, ...);

And its definition usually uses the stdarg system to handle the extra arguments, which looks like:

#include <stdarg.h>

int printf(const char* format, ...)
{
    va_list ap;
    int result;
    va_start(ap, format);
    result = vprintf(format, ap);
    va_end(ap);
    return result;
}

If you're on a platform with standard C calling conventions, that va_end(ap) macro usually turns into a do-nothing. In this case, you can get away with passing extra arguments to a function. But on some platforms, the va_end() call is required to restore the stack to a predictable state (i.e. where it was before the call to va_start); in those cases, your functions will not leave the stack the way it found it (it won't pop enough arguments back off the stack) so your calling function could, for example, crash on exit when it fetches a bogus value for a return address.

Mike DeSimone
  • 41,631
  • 10
  • 72
  • 96
2

Your functions must certainly be using the cdecl calling convention (http://en.wikipedia.org/wiki/X86_calling_conventions#cdecl). This pushes arguments on the stack in reverse order, from right to left, ensuring that the last argument can be easily located (top of the stack) and used to interpret the remainder, such as a printf format string. It is also the responsibility of the caller to clean up the stack, which is a bit less compact than the function itself doing so (as in pascal/stdcall convention), but ensures that variable argument lists can be used, and implies that trailing arguments can be ignored.

Scott Jones
  • 2,880
  • 13
  • 19
  • cdecl isn't the only one that would work, practically any caller-cleanup calling convention would work just fine here too. – Richard J. Ross III May 10 '13 at 12:55
  • 1
    Yes, but as this is a 'c' question, most likely cdecl. – Scott Jones May 10 '13 at 12:56
  • You can have variable argument lists in pascal/stdcall conventions. You just have a nontrivial definition of `va_end`. It might not work if you don't get the right number of arguments, though. – Mike DeSimone May 10 '13 at 13:06
  • Wonderful! I hadn't remembered the term calling convention, although now I do recall learning this stuff over a decade ago! That is just what I was after - not that I'm leaving the casting in place, but it is good to know that it will not cause any issues on all the existing systems based on this. – asc99c May 10 '13 at 13:23