If I typedef a function pointer of the form:
typedef void (*pointer_type)(uint8_t * data);
and define some functions of the form:
void function_a(uint8_t * const data) {}
void function_b(const uint8_t * data) {}
void function_c(const uint8_t * const data) {}
which of the following definitions are valid (ie will compile without errors or warnings on all systems and will always run correctly when called on non-const data)?
pointer_type f_a = function_a;
pointer_type f_b = function_b;
pointer_type f_c = function_c;
What about the reverse? If the pointer type's argument is a const pointer or points to const data, which of the functions can it point to?
I've tried googling this but putting in the words "function pointer" and "const" gets swamped by explanations of how a constant function pointer works. Explanations based on the C standard would be appreciated, along with any relevant differences between versions of the C standard.
… which of the following definitions are valid (ie will compile without errors or warnings on all systems and will always run correctly when called on non-const data)?
Technically none are valid in that sense because the C standard allows implementations to generate warnings for anything, so there is, conceptually at least, a compiler that issues a warning for each of the definitions. Instead, I will consider whether the C standard requires diagnostics for the statements.
The standard requires a diagnostic for constraint violations. Our concern here is the initializations in the definitions. C 2018 6.7.9 11 tells us that, in these initializations, the constraints for simple assignment apply:
The initializer for a scalar shall be a single expression, optionally enclosed in braces. The initial value of the object is that of the expression (after conversion); the same type constraints and conversions as for simple assignment apply, taking the type of the scalar to be the unqualified version of its declared type.
C 2018 6.5.16.1 1 lists constraints for simple assignment. Of them, the only one applicable to assigning to a pointer to a function type from a pointer that is not a null pointer constant is:
… 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;…
In each case, the right operand is a function designator, and this function designator is automatically converted to a pointer to the function. The result of this is an unqualified pointer.
So, for pointer_type f_a = function_a;, we have a void (*)(uint8_t * data) for the left operand and void (*)(uint8_t * const data) for the right operand. Both are unqualified pointers, so the remaining question is whether these are compatible types. C 2018 6.7.6.1 2 tells us:
For two pointer types to be compatible, both shall be identically qualified and both shall be pointers to compatible types.
So now the question is whether void ()(uint8_t * data) (a function type, as the interior * has been removed) is compatible with void ()(uint8_t * const data) (ditto). C 2018 6.7.6.3 15 speaks to compatibility of function types. With parts not relevant to the matter at hand elided, it says:
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…; corresponding parameters shall have compatible types… (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.)
Both function types have parameter lists, and each list has one parameter. They are uint8_t * data and uint8_t * const data. The right one is qualified with const, and we are told to consider its unqualified version. Its unqualified version is uint8_t * data, and then the two have the same type. C 2018 6.2.7 1 says:
Two types have compatible type if their types are the same…
Therefore pointer_type f_a = function_a; satisfies the constraints and does not require a diagnostic.
For pointer_type f_b = function_b;, the same analysis brings us to uint8_t * data on the left and const uint8_t * data on the right. These are not the same, so the rule about same types being compatible does not apply. We consider the rule about compatibility of pointers, quoted above. uint8_t * data and const uint8_t * data are pointers to uint8_t and const uint8_t. Here C 2018 6.7.3 11 applies:
For two qualified types to be compatible, both shall have the identically qualified version of a compatible type; the order of type qualifiers within a list of specifiers or qualifiers does not affect the specified type.
These types are not identically qualified, so they are not compatible. The constraints for initialization are not satisfied, so the compiler must issue a diagnostic.
In pointer_type f_c = function_c;, the analysis leads to the same conclusion. The latter const in void function_c(const uint8_t * const data) is ignored by 6.7.6.3 15, but we end with uint8_t const * data and uint8_t * data, which are not compatible, so a diagnostic is required.
If the pointer type's argument is a const pointer or points to const data, which of the functions can it point to?
As we have seen, whether the parameter (not argument; an argument is what is passed in a function call, not what is declared in a function declaration) has a const qualifier is irrelevant, as it is ignored in considering function compatibility.
If the parameter points to a type qualified with const, then it is compatible with an argument that points to a type qualified with const and is otherwise compatible, and it is not compatible with an argument that points to a type not qualified with const.
Note that this differs from calling a function: If an argument of type pointer-to-non-const is passed for a parameter of type point-to-const, that is allowed, because the rules for simple assignment are used, which allows adding qualifiers to pointed-to types. But when assigning one function pointer to another, the test for parameter compatibility does not allow this addition of qualifiers.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With