3

Consider the following code:

#include <stdio.h>

typedef int (*addif_fn_t) (int, int, int);

int add (int a, int b) {
    return a + b;
}

int addif (int a, int b, int cond) {
    return cond ? (a + b) : 0;
}

int main() {
    addif_fn_t fn;

    fn = addif;
    printf("addif:\t%d %d\n", fn(1, 2, 1), fn(1, 2, 0));

    fn = (addif_fn_t)add;
    printf("add:\t%d %d\n", fn(1, 2, 1), fn(1, 2, 0));

    return 0;
}

On any Intel machine using the standard C calling convention, this results in:

addif:  3 0
add:    3 3

The question is: how portable is this idiom? Does C allow calling a function with more parameters than it accepts?

My guess is that it depends entirely upon the ABI and how it determines out where both the function arguments and local variables are stored. More to the point, this is likely not portable code. But I have seen this idiom used several times in real codebases.

md5i
  • 3,018
  • 1
  • 18
  • 32
  • 2
    Related: [Are these compatible function types in C?](http://stackoverflow.com/questions/24743887/are-these-compatible-function-types-in-c) – Shafik Yaghmour Jul 23 '14 at 21:27
  • In C, `int main()` can take as many arguments as you want. While `int main(void)` cannot take any argument. – Peng Zhang Jul 23 '14 at 21:34
  • 2
    Most likely the arguments are inserted on the stack right to left and the caller cleans up the stack. That is the reason why it works. But it depends on the calling convention so I wouldn't call it portable. – imreal Jul 23 '14 at 21:38
  • 2
    http://stackoverflow.com/questions/559581/casting-a-function-pointer-to-another-type – imreal Jul 23 '14 at 21:42
  • @PengZhang: That is incorrect. `main()` is a special case. – Dietrich Epp Jul 23 '14 at 21:48
  • @DietrichEpp: Given `int main()`, a compiler will typically *permit* calls with arbitrary arguments. Such calls may have undefined behavior. – Keith Thompson Jul 23 '14 at 21:51
  • portable maybe, recommended? hell no. – AndersK Jul 23 '14 at 21:53
  • The callee has no reason to expect to cleanup the extra stacks, but the caller expects the stack to be back to normal. If you want to see it break, try `foo( x, bar ( to, many, params, here), z);` – technosaurus Jul 24 '14 at 04:45

1 Answers1

7

As a practical matter, I don't know how "portable" it is (in the sense of whether it will behave as you expected to under existing implementations, or at least under the subset of implementations you're concerned about).

As far as the C standard is concerned, it's not portable at all. Your program has undefined behavior, because it calls a function via an expression of a type (int(*)(int, int) that differs from the actual type of the function (int(*)(int, int, int)). (The former is the type used to define your add function; the latter is the type of the expression fn used as the prefix of the call.)

This is stated in the C standard, section 6.5.2.2 paragraph 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.

(The link is to a PDF of the N1570 draft of the C11 standard. You'll find similar, or probably identical, wording in other editions of the standard.)

My advice: Don't Do That.

Note, however, that excess arguments to variadic functions (functions like printf() that are declared with a , ...) are quietly ignored. For example, this is perfectly legal:

printf("Ignore the arguments\n", 10, 20, 30);

If you really need to be able to call a function without knowing how many arguments it expects, this might be a workable approach (though you'll lose compile-time type checking for any arguments matching the , ...).

For non-variadic functions, you can freely convert function pointers from one type to another, but you have to convert back to the correct type for each call.

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631