Jackie
Jackie

Reputation: 71

Reason why int(*g)(int) into int(*h)(char) causes error?

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

Answers (4)

dbush
dbush

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

Daniel Langr
Daniel Langr

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

Assigning g into f (f = g) will not cause an error in C/C++, but assigning g into h (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

Lev M.
Lev M.

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

Related Questions