Reputation: 71
I am studying programming language principles and I have a question with C and C++.
int (*f)(int);
int (*g)(int);
int (*h)(char);
f = g; // ok
h = g; // warning in C, error in C++
Assigning g
into f
(f = g
) will not cause an error in C or C++, but assigning g
into h
(h = g
) would generate a compiler warning in C and compile error in C++.
I heard that the char
type is usually auto-casted into int
in C++, so I thought this would not cause an error.
Can someone explain this to me?
Upvotes: 4
Views: 185
Reputation: 224062
For two function types to be compatible, the returns types must match and the number and types of the arguments must match. The conversion rules that apply between integer types do not apply to arguments of function types.
Section 6.7.6.3p15 of the C standard states the following:
For two function types to be compatible, both shall specify compatible return types. 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 the determination of type compatibility and of a composite type, each parameter declared with function or array type is taken as having the adjusted type and each parameter declared with qualified type is taken as having the unqualified version of its declared type.)
The types int
and char
are not compatible with each other, therefore a function with one int
parameter and another with one char
parameter are not compatible with each other, making the assignment in your example invalid.
C compilers will typically warn when attempting a conversion between incompatible pointer types. Attempting to defererence h
in your example by calling the function will invoke undefined behavior.
Upvotes: 3
Reputation: 23497
You might also wonder why those function pointers are not implicitly-convertible into each other. Consider the following function definition:
void f(void(*f)(char))
{
f('A');
}
A compiler now needs to generate a machine code for this function. In my case, it was:
mov rax, rdi
mov edi, 65
jmp rax
This machine code would generally not work if, as an argument of f
, would be passed a pointer to a function that has a parameter of a different type. Here, the argument to f
is passed through the edi
register. By coincidence, that would work also for a function that has a parameter of type int
with my implementation. But this is purely an implementation issue and the standard rules may not be driven by it.
For illustration, if I would pass, as an argument of f
, a pointer to the function that has a parameter of type long
, this would stop working, since then the argument would not be passed to f
through the rdi
register (as this function would expect).
Upvotes: 1
Reputation: 133929
Assigning
g
intof
(f = g
) will not cause an error in C/C++, but assigningg
intoh
(h = g
) would generate a compiler warning in C and compile error in C++.
This is incorrect. A program that attempts to do h = g
is an invalid program, be it C or C++. However the C standard explicitly mentions that a C compiler is allowed to successfully compile an invalid program, provided that it issues a diagnostic message for every violation of certain rules. You got your warning and then the C compiler proceeded anyhow.
In fact, C++ standard contains a very similar wording:
If a program contains a violation of any diagnosable rule or an occurrence of a construct described in this document as “conditionally-supported” when the implementation does not support that construct, a conforming implementation shall issue at least one diagnostic message.
[...]
A conforming implementation may have extensions (including additional library functions), provided they do not alter the behavior of any well-formed program. Implementations are required to diagnose programs that use such extensions that are ill-formed according to this document. Having done so, however, they can compile and execute such programs.
But customarily C++ compilers are by default more strict when it comes to diagnosable violations and the compilation will fail by default whereas the C compiler from the same vendor would successfully compile such a translation unit.
Note that both C and C++ has a provision that you can do the conversion with a cast, i.e. h = (int (*)(char))g
; but you again must not call a function through h
without casting it back to int (*)(int)
first!
Upvotes: 3
Reputation: 6269
When you define a pointer to a function, you essentially define a new variable type.
So in your example, f
and g
are the same type, because they are defined identically, but h
is another type, because it is defined differently.
While both C and C++ compilers know how to convert between char
and int
types, as those are built in to the language, they don't know how to convert between g
and h
types, because you defined those.
The fact that char
is part of g
definition does not help here, because the compiler has no right to alter the internals of your definitions.
Consider the kind of bugs this could cause: If you use a calling convention where all arguments are put on the stack, sending a char to a function that expects int would cause it to get a wrong value, as it will be reading extra bytes from the stack that are not part of the sent value.
Upvotes: 1