32

Consider the following C program:

int f() { return 9; }
int main() {
  int (*h1)(int);
  h1 = f; // why is this allowed?                                               
  return h1(7);
}

According to the C11 Standard, Sec. 6.5.16.1, in a simple assignment, "one of the following shall hold", and the only relevant one in the list is the following:

the left operand has atomic, qualified, or unqualified pointer type, and (considering the type the left operand would have after lvalue conversion) both operands are pointers to qualified or unqualified versions of compatible types, and the type pointed to by the left has all the qualifiers of the type pointed to by the right;

Moreover, this is a "constraint", meaning, a conforming implementation must report a diagnostic message if it is violated.

It seems to me that this constraint is violated in the assignment in the program above. Both sides of the assignment are function pointers. So the question is, are the two function types compatible? This is answered in Sec. 6.7.6.3:

For two function types to be compatible, both shall specify compatible return types.146) Moreover, the parameter type lists, if both are present, shall agree in the number of parameters and in use of the ellipsis terminator; corresponding parameters shall have compatible types. If one type has a parameter type list and the other type is specified by a function declarator that is not part of a function definition and that contains an empty identifier list, the parameter list shall not have an ellipsis terminator and the type of each parameter shall be compatible with the type that results from the application of the default argument promotions. If one type has a parameter type list and the other type is specified by a function definition that contains a (possibly empty) identifier list, both shall agree in the number of parameters, and the type of each prototype parameter shall be compatible with the type that results from the application of the default argument promotions to the type of the corresponding identifier.

In this case, one of the types, that of h1, has a parameter type list; the other, f, does not. Hence the last sentence in the quote above applies: in particular, "both shall agree in the number of parameters". Clearly h1 takes one parameter. What about f? The following point occurs just before the above:

An empty list in a function declarator that is part of a definition of that function specifies that the function has no parameters.

So clearly f takes 0 parameters. So the two types do not agree in the number of parameters, the two function types are incompatible, and the assignment violates a constraint, and a diagnostic should be issued.

However, both gcc 4.8 and Clang emit no warnings when compiling the program:

tmp$ gcc-mp-4.8 -std=c11 -Wall tmp4.c 
tmp$ cc -std=c11 -Wall tmp4.c 
tmp$

By the way, both compilers do issue warnings if f is declared "int f(void) ...", but this should not be necessary based on my reading of the Standard above.

The questions:

Q1: Does the assignment "h1=f;" in the program above violate the constraint "both operands are pointers to qualified or unqualified versions of compatible types"? Specifically:

Q2: The type of h1 in the expression "h1=f" is pointer-to-T1 for some function type T1. What exactly is T1?

Q3: The type of f in the expression "h1=f" is pointer-to-T2 for some function type T2. What exactly is T2?

Q4: Are T1 and T2 compatible types? (Please quote appropriate sections of the Standard or other documents to support the answer.)

Q1', Q2', Q3', Q4': Now suppose the declaration of f is changed to "int f(void) { return 9; }". Answer questions 1-4 again for this program.

Steve Siegel
  • 504
  • 5
  • 11
  • 1
    If I put that into clang I get: functCheck.cxx:4:6: error: assigning to 'int (*)(int)' from incompatible type 'int ()': different number of parameters (1 vs 0) h1 = f; // why is this allowed... ^ ~ 1 error generated. – kwierman Jul 14 '14 at 19:07
  • 6
    @user2950041: This is a C question. C has different notions of declaration, prototype and definition than C++. – Kerrek SB Jul 14 '14 at 19:10
  • @KerrekSB Of course. That's why I used a C compiler, I just have a nasty habit of ending files with .cxx. The compiler doesn't care – kwierman Jul 14 '14 at 19:15
  • 7
    @user2950041: Actually, the compiler does care. Clang infers the language from the file extension. (For example, you can put templates in your `cxx` file, and compile it just fine with `cc`; if you name the file like `foo.xyz` it will not compile). – nneonneo Jul 14 '14 at 19:17
  • 8
    Not that I find anything supporting this in your quotes above, but in *plain old c* an empty parameter list meant that you could supply any number of arguments. I.e. void f() != void f(void). I'm not comfortable enough with the deep dungeons of C to know if f() == f(...), but I doubt it as varargs need at least the first argument to hook onto. – Stian Svedenborg Jul 14 '14 at 19:28
  • 1
    @user2950041 I finally do think it's a bug, what about filing a bug in gcc Bugzilla? – ouah Jul 14 '14 at 20:40
  • 1
    as far as i can tell this should be reported but the line `h1 = (int (*)())f;` (or an equivalent set of statements that assigns `f` to a variable of type `int (*)()`) need not be reported since the type of the expression to the right of the assignment comes from a declarator and not a definition. This seems like really weird behavior, and i'm not surprised gcc would get this wrong. – Steve Cox Jul 14 '14 at 20:55
  • @Stian: iirc the `f()` style represents an arbitrary set of parameters in function declarations, but I'm far from sure whether this holds at function definition time as well. – MvG Jul 14 '14 at 22:11
  • Probably nobody noticed this piece of wording until now, I would have just assumed that it was meant to be equivalent wording for function pointers to actual functions – M.M Jul 15 '14 at 00:53
  • @MvG It does hold at definition time too, there is a different piece of standard text that covers all that in detail – M.M Jul 15 '14 at 00:54
  • 1
    None of the 4 Answers below answers my question so let me clarify it. Which of the following is the case: (a) this is a bug in Clang and gcc, or (b) I am mis-reading the Standard. If (b), how exactly? I suppose a third possibility is (c) neither Clang nor gcc claim to be conforming implementations, and this is just an example of how they fail to be. – Steve Siegel Jul 15 '14 at 18:08
  • 1
    [The GCC docs](https://gcc.gnu.org/onlinedocs/gcc/Non-bugs.html#Non-bugs) list this as something they don't want to change. Their reasoning, however, sounds like they are either unaware that C99 and C11 require a diagnostic; or we are misinterpreting the standard and Gcc is conforming in this respect. Ad (c): They [claim](https://gcc.gnu.org/wiki/C11Status) to be standard conforming (with the right options). – mafso Jul 15 '14 at 18:22
  • 1
    Filed bug report with LLVM: http://llvm.org/bugs/show_bug.cgi?id=20313 – Steve Siegel Jul 15 '14 at 23:59
  • C11 6.5.1(2): "An identifier is a primary expression, provided it has been declared as designating an object (in which case it is an lvalue) or a function (in which case it is a function designator).". 6.3.2.1(4): "A function designator is an expression that has function type. Except when it is the operand of the sizeof operator, the _Alignof operator,65) or the unary & operator, a function designator with type ‘‘function returning type’’ is converted to an expression that has type ‘‘pointer to function returning type’’." – Steve Siegel Jul 16 '14 at 01:37
  • @SteveSiegel in light of Shafik's answer just posted, perhaps the bug report should be retracted. ([DR316 Q2](http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_316.htm) is this question, and the Committee resolution is that the code should be accepted) – M.M Jul 16 '14 at 04:07
  • I clarified the exact question by adding 8 very specific sub-questions to the end. I hope this will encourage people to clarify the answer(s). – Steve Siegel Jul 19 '14 at 16:06

5 Answers5

8

These two defect reports address your issue:

Defect report 316 says (emphasis mine going forward):

The rules for compatibility of function types in 6.7.5.3#15 do not define when a function type is "specified by a function definition that contains a (possibly empty) identifier list", [...]

and it has a similar example to the one you give:

void f(a)int a;{}
void (*h)(int, int, int) = f;

and it goes on to say:

I believe the intent of the standard is that a type is specified by a function definition only for the purposes of checking compatibility of multiple declarations of the same function; when as here the name of the function appears in an expression, its type is determined by its return type and contains no trace of the parameter types. However, implementation interpretations vary.

Question 2: Is the above translation unit valid?

and the answer from the committee was:

The Committee believe the answers to Q1 & 2 are yes

This was between C99 and C11 but the committee adds:

We have no intention of fixing the old style rules. However, the observations made in this document seem to be generally correct.

and as far a I can tell C99 and C11 do not differ greatly in the sections you have quoted in the question. If we further look into defect report 317 we can see that it says:

I believe the intent of C is that old-style function definitions with empty parentheses do not give the function a type including a prototype for the rest of the translation unit. For example:

void f(){} 
void g(){if(0)f(1);}

Question 1: Does such a function definition give the function a type including a prototype for the rest of the translation unit?

Question 2: Is the above translation unit valid?

and the committees response was:

The answer to question #1 is NO, and to question #2 is YES. There are no constraint violations, however, if the function call were executed it would have undefined behavior. See 6.5.2.2;p6.

This seems to hinge on the fact that it is underspecified whether a function definition defines a type or a prototype and therefore means there is no compatibility checking requirements. This was originally the intent with old style function definitions and the committee will not clarify further probably because it is deprecated.

The committee points out that just because the translation unit is valid does not mean there is no undefined behavior.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • In DR317 it is clearly 1.NO, 2.YES; the standard clearly specifies that `void f() { }` does not form a prototype. – M.M Jul 16 '14 at 04:04
  • DR316 Q2 is the same question as this SO post asks; and the resolution is that the committee believes the code should be accepted (and so by inference , that the text quoted from 6.7.6.3 is defective) – M.M Jul 16 '14 at 04:04
  • Thanks, this definitely addresses the issue, but I'm still confused about the "intent". In the assignment "h1=f", what is the type of h1, and what is the type of f, and why are the base types of those two pointer types compatible? Ditto if the we add the "void" to the definition of f, in which case everyone (including the compilers) agree that the base types are incompatible. – Steve Siegel Jul 16 '14 at 11:49
  • @SteveSiegel I think what is happening is that `gcc` and `clang` are treating *old style K&R* definitions differently. So the problem you outline seem to exist only for those forms. It seems like the `void` form is not considered *old style*. That may be arbitrary, the defect reports are not very detailed, probably on purpose. I am guessing only the gcc and llvm teams could fully explain this. – Shafik Yaghmour Jul 16 '14 at 12:57
  • I still don't understand what the types of h1 and f are in the assignment expression, even after reading the defect reports. I've added specific questions Q1-Q4 and Q1'-Q4' to the main question above in the hope that this answer can be improved to answer those specific questions. – Steve Siegel Jul 20 '14 at 14:39
3

Historically, C compilers generally handled argument passing in a way that guaranteed that extra arguments would be ignored, and also only required that programs passed arguments for parameters that were actually used, thus allowing e.g.

int foo(a,b) int a,b;
{
  if (a)
    printf("%d",b);
  else
    printf("Unspecified");
}

to be safely callable via either foo(1,123); or foo(0);, without having to specify a second argument in the latter case. Even on platforms (e.g. classic Macintosh) whose normal calling convention wouldn't support such a guarantee, C compilers generally default to using a calling convention that would support it.

The Standard makes clear that compilers are not required to support such usage, but requiring implementations to forbid them would have not only broken existing code, but would also have made it impossible for those implementations to produce code that had been as efficient as what was possible in pre-standard C (since application code would have to be changed to pass useless arguments, which compilers would then have to generate code for). Making such usage Undefined Behavior relieved implementations of any obligation to support it, while still allowing implementations to support it if convenient.

supercat
  • 77,689
  • 9
  • 166
  • 211
1

Not a direct answer to your question, but the compiler simply generates assembly for pushing the value into the stack before calling the function.

For example (using VS-2013 compiler):

mov         esi,esp
push        7
call        dword ptr [h1]

If you add a local variable in this function, then you can use its address in order to find the values that you pass whenever you call the function.

For example (using VS-2013 compiler):

int f()
{
    int a = 0;
    int* p1 = &a + 4; // *p1 == 1
    int* p2 = &a + 5; // *p2 == 2
    int* p3 = &a + 6; // *p3 == 3
    return a;
}

int main()
{
    int(*h1)(int);
    h1 = f;
    return h1(1,2,3);
}

So in essence, calling the function with additional arguments is completely safe, as they are simply pushed into the stack before the program-counter is set to the address of the function (in the code-section of the executable image).

Of course, one could claim that it might result with a stack-overflow, but that can happen in any case (even if the number of arguments passed is the same as the number of arguments declared).

barak manos
  • 29,648
  • 10
  • 62
  • 114
-1

For functions without declared parameters no parameters/parameter types are inferred by the compiler. The following code is essentially the same:

int f()
{
    return 9;
}

int main()
{
    return f(7, 8, 9);
}

I believe this has something to do with the underlying way variable length arguments are supported, and that () is basically identical to (...). Taking a closer look at the generated object code shows that the arguments to f() still get pushed onto the registers used to call the function, but since they are referenced in the function definition they simply aren't used inside the function. If you want to declare a parameter that does not support arguments it's a bit more proper to write it as such:

int f(void)
{
    return 9;
}

int main()
{
    return f(7, 8, 9);
}

This code will fail to compile in GCC for the following error:

In function 'main':
error: too many arguments to function 'f'
Kenneth Wilke
  • 4,631
  • 1
  • 15
  • 7
  • 1
    No, `()` is not basically identical to `(...)`, though they may well both be implemented the same way. Two functions, one defined with `()` and one with `(...)`, are incompatible; calling a variadic function with no visible prototype has undefine behavior. (Most C compilers use the same calling conventions for both, for historical reasons and to satisfy ABIs.) – Keith Thompson Jul 14 '14 at 22:49
  • This has not much to do with the function pointer topic (except perhaps to illustrate that the wording for the function pointers having "compatible" types is different to the behaviour for actually calling functions) – M.M Jul 15 '14 at 00:52
-4

try to use __stdcall before function declaration - and it wouldn't compile.
The reason is that function call is __cdecl by default. It means (beside other features) that caller clears stack after call. So, caller function may push on stack everything it wants, because it knows what it had pushed and will clear stack in right way.
__stdcall means (beside other things) that callee would clean stack. So number of arguments must match.
... sign says to compiler that number of arguments varies. If declared as __stdcall, then it would be automatically substituted with __cdecl, and you still can use as many arguments as you want.

That is why compiler warns, but not halt.

Examples
Error: stack corrupted.

#include <stdio.h>

void __stdcall allmyvars(int num) {
    int *p = &num + 1;
    while (num--) {
        printf("%d ", *p);
        p++;
    }  
}

void main() {
    allmyvars(4, 1, 2, 3, 4);
}

Works

#include <stdio.h>

void allmyvars(int num) {
    int *p = &num + 1;
    while (num--) {
        printf("%d ", *p);
        p++;
    }  
}

void main() {
    allmyvars(4, 1, 2, 3, 4);
}

For this example you have normal behaviour, which is not interconnected with standard. You declare pointer to function, after that assign this pointer and it leads to implicit type conversion. I wrote why it works. In c you also can write

int main() {
  int *p;
  p = (int (*)(void))f; // why is this allowed?      
  ((int (*)())p)();
  return ((int (*)())p)(7);
}

And it is still part of standard, but other part of standard of course. And nothing happens, even if you assign pointer to function to pointer to int.

Ivan Ivanov
  • 2,076
  • 16
  • 33
  • 1
    -1, the standard knows nothing of stdcall and calling conventions. – Matteo Italia Jul 15 '14 at 05:44
  • Right you are. But what is wrong with the answer? Isn't it right? – Ivan Ivanov Jul 15 '14 at 05:47
  • 1
    The point is that it doesn't answer the question, that to me is a "standard interpretation" request. It's not clear what point you demonstrate in relation to that, yes, if you use a particular nonstandard extension everything breaks, but the question was not "does it happen to work in VC++", but "is it *guaranteed* to work in plain C? Shouldn't it emit warnings?" And to these questions an authoritative answer can be given only by quoting the Standard. – Matteo Italia Jul 15 '14 at 05:54
  • What is a PLAIN C, please? Tell me name of plain c compiler. If you have compiler of PLAIN C, just compile project on this compiler. If it is compiled, then it works fine, if not - then this code is invalid. I explained why it works, not if this a standard. You have read part of standard. If still have questions - read it again. It is univocal. – Ivan Ivanov Jul 15 '14 at 06:08
  • "Plain C" is what the standard specifies. There is no plain C compiler at all (or, if you prefer, there's no reference implementation that is correct by definition), which is why this kind of question cannot be answered by performing tests on a particular implementation (which can be wrong - it's not that rare when talking about dusty corners of the language), especially if you test unrelated stuff. – Matteo Italia Jul 15 '14 at 06:13
  • It works because of implicit type casting. No, it is not allowed in standard. But in this case standard have nothing with it, because you call proper pointer with proper parameters. For example, h1 = (int (*)(int))f; gives no error and no warnings. But why it works? I answered this question above. But why it happens - because linker doesn't know about that. And what to do? to hint compiler declaring calling convention. – Ivan Ivanov Jul 15 '14 at 06:26