I have a function pointer whose function is declared as expecting char * arguments.Into it, I'd like to save a pointer to a function declared as taking char const* arguments.
I guess I can either use a wrapper or a cast.
Casts seem more straightforward, but can I legally call the result of such a function pointer cast?
Example code below:
static int write_a(char * X){
return 0;
}
static int write_b(char const* X){
return 0;
}
static int wrapped_write_b(char * X){
return write_b(X);
}
typedef int (*write_fn)(char * );
write_fn a = write_a;
write_fn b = wrapped_write_b;
write_fn b1 = (write_fn)write_b; //is b1 legally callable?
This is undefined behavior - You can use a pointer to call a function of another type only if the types are compatible (6.3.2.3/8):
A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer. If a converted pointer is used to call a function whose type is not compatible with the referenced type, the behavior is undefined.
Two functions have compatible types if (simplified version) they have same return and arguments are compatible (6.7.6.3, Semantics/15):
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.
A const char * is not compatible with a char * (6.7.6.1, Semantics/2):
For two pointer types to be compatible, both shall be identically qualified and both shall be pointers to compatible types.
Since const char * and char * are not identically qualified, they are not compatible, and calling write_b through b is undefined behavior.
Strictly speaking, it is not allowed.
A pointer-to-something is not compatible with a pointer-to-qualified-something. Because a pointer-to-qualified-something is not a qualified type of pointer-to-something
The same applies for
pointer-to-function-accepting-something
and
pointer-to-function-accepting-qualified-something.
This can be found through C11 6.2.7 compatible type:
Two types have compatible type if their types are the same.
Additional rules for determining whether two types are compatible are
described in 6.7.2 for type specifiers, in 6.7.3 for type
qualifiers...
Where 6.7.3 is the relevant part. It says
For two qualified types to be compatible, both shall have the identically qualified version of a compatible type;
The conversion chapter 6.3.2.3 does not contradict this:
A pointer to a function of one type may be converted to a pointer to a function of another
type and back again; the result shall compare equal to the original pointer. If a converted
pointer is used to call a function whose type is not compatible with the referenced type,
the behavior is undefined.
EDIT
As noted in the answer by Holt, the compatibility of two functions is explicitly described in 6.7.6.3/15.
I still think that a wrapper function is the best solution. The root of the problem is that write_a isn't const-correct. If you can't change that function, then write a wrapper around it.
write_fn b1 = (write_fn)write_b; //is this legal?
Is what legal?
Function pointer types involved in this cast are not compatible.
Yet casting function pointer to an incompatible function pointer type is perfectly legal. However, the only thing you can do with such forcefully converted pointer is convert it back to the original type. The language specification guarantees that such round-trip conversion will preserve the original pointer value. This is why we can use, say, void (*)(void) as a "universal storage type" for function pointers (like void * for data pointers). It can be used for storing function pointers of any type (but not for calling the functions). To perform such pointer storage (and retrieval) we'll have to use explicit casts, just like the one in your code. There's nothing illegal about it.
Meanwhile, trying to call the function through b1 will result in undefined behavior, specifically because the pointer type is not compatible with the actual function type.
In your question you clearly state that you want to "save" the pointer to that function. As long as we are talking only about "saving" (storing) the pointer, your code is perfectly flawless.
Related
Let's consider the next code:
#include <stdlib.h>
int some_api_function_with_multiple_results( int** OUT_result ) {
*OUT_result = malloc( 123 * sizeof(**OUT_result) );
return 42;
}
int main(void) {
const int* numbers; /* don't ask me why it's 'const' please, I'm just curious! */
int some_result = some_api_function_with_multiple_results( (int**)&numbers );
free( (void*)numbers );
return EXIT_SUCCESS;
}
Notice the const int* pointer (NOT int* const!!) that is passed by reference to the function expecting a pointer to int* (that is, int**) as a variable for an additional return value.
This compiles fine and without any warnings with GCC 9.4.0 in C90 mode (-std=c90). And the C90 standard (ansi-iso-9899-1990-1.pdf) states the following:
6.1.2.5 Types
A pointer to void shall have the same representation and alignment requirements as a pointer to a character type. Similarly, pointers to qualified or unqualified versions of compatible types shall have the same representation and alignment requirements.16 Pointers to other types need not have the same representation or alignment requirements.
16 The same representation and alignment requirements are meant to imply interchangeability as arguments to functions, return values from functions, and members of unions.
It's worth noting that C89 draft (http://port70.net/~nsz/c/c89/c89-draft.html#3.1.2.5) imposes a much more restrictive constraint. I wonder if this is the difference between the draft standard and its final version, or is it between C89 and C90? (update: I found a scanned copy of C89 called fipspub160.pdf and it has the same wording as in C90, so it appears to be a draft inaccuracy)
3.1.2.5 Types
A pointer to void shall have the same representation and alignment requirements as a pointer to a character type. Other pointer types need not have the same representation or alignment requirements.
It's also well-known that a value of type T* can be assigned to a variable (or passed as a function parameter) of type const T* just fine and without any explicit coercion such as typecasting:
3.3.16.1 Simple assignment
One of the following shall hold:42
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;
42 The asymmetric appearance of these constraints with respect to type qualifiers is due to the conversion (specified in 3.2.2.1) that changes lvalues to "the value of the expression" which removes any type qualifiers from the top type of the expression.
3.3.2.2 Function calls
If the expression that denotes the called function has a type that includes a prototype, the arguments are implicitly converted, as if by assignment, to the types of the corresponding parameters.
The actual thing that is bothering me is that const int* and int* types are not compatible according to both C89 and C90 standards (and C99 as well, so it wasn't an errata that could be fixed in both intermediate Technical Corrigenda or numerous Defect Reports of the standardization committee). So I'm not sure if the following statements make my code having undefined behaviour:
3.5.4.1 Pointer declarators
For two pointer types to be compatible, both shall be identically qualified and both shall be pointers to compatible types.
3.5.3 Type qualifiers
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.
3.1.2.6 Compatible type and composite type
All declarations that refer to the same object or function shall have compatible type; otherwise the behaviour is undefined.
The question is, does it actually take place? Or is it still valid code?
in the following example I make a CAST of a function without arguments in a pointer to a function that should receive an argument. Assuming that it gives the desired result, is it possible that this procedure causes some malfunction?
online test: https://onlinegdb.com/SJ6QzzOKI
typedef void (*Callback)(const char*);
Callback cb;
void inserisce_cb(void* c) {
cb=c;
}
void esegue_cb(){
cb("pippo");
}
void scriveTitolo(const char* titolo) {
Uart_Println(titolo);
}
void scriveTitolo2() {
Uart_Println("pluto");
}
void main(){
inserisce_cb(scriveTitolo);
esegue_cb();
inserisce_cb(scriveTitolo2);
esegue_cb();
}
Converting a pointer to a function to another pointer to a function is defined by the c standard, but using the resulting pointer to call a function with an incompatible type is not, per C 6.3.2.3 8:
A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer. If a converted pointer is used to call a function whose type is not compatible with the referenced type, the behavior is undefined.
The declaration void scriveTitolo2() { … } defines a function that does not have a parameter type list (it uses the old C style of an identifier list, with that list being empty) and that takes no arguments. A Callback pointer points to a function that has a parameter type list and takes a const char * argument. These are incompatible per C 2018 6.7.6.3 15:
For two function types to be compatible,… 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,…
Since they do not agree in the number of parameters, they are incompatible.
The above speaks only to the issue of converting from void (*)() to void (*){const char *) and using the result to call the function. There is a separate issue in that the function pointer is passed to inserisce_cb, which takes an argument of type void *, which is a pointer to an object type. The C standard does not define the behavior of converting a pointer to a function type to a pointer to an object type. To remedy this, inserisce_cb should be declared to take a pointer to a function type, such as void inserisce_cb(Callback c).
If scriveTitolo2 can be changed, then the compatibility issue can be resolved by changing it to take a const char * parameter that is unused, changing its definition to void scriveTitolo2(const char *).
(Note that it is preferable to declare scriveTitolo2 with the modern C style, as void scriveTitolo2(void) { … }, rather than without the void. This is unrelated to the question, as it would not make the function types compatible, but this format of declaration provides more information to the compiler in many circumstances.)
Additional thoughts to Eric's answer, which holds true for C99 as well:
If you call a function with an argument list not compatible to the function's parameter list, this is according to C99 §6.5.2.2 (6) undefined behavior.
It may work, depending on your compiler's ABI. There are compilers that let the called function clean up the stack, other compilers let the caller clean up. The former case will most likely crash, the latter ... who knows.
You can declare your scriveTitolo2 with an ignored parameter:
void scriveTitolo2(const char*) {
/* ... */
}
And everyone is happy: you and the compiler.
I have a generic set of elements provided as a library.
/** Type for defining the set */
typedef struct Set_t *Set;
/** Element data type for set container */
typedef void* SetElement;
/** Type of function for copying an element of the set */
typedef SetElement(*copySetElements)(SetElement);
To create the set I must provide a pointer to a function that handles the copying of elements that I intend to use the set for.
Set setCreate(copySetElements copyElement);
I wrote the following type and copy function:
typedef struct location_t {
char *name;
} *Location;
Location locationCopy(Location location){
Location new_location = locationCreate(location->name);
return new_location;
}
*Obviously I simplified everything to focus the discussion.
When I call:
Set locations = setCreate(locationCopy);
I get a compiler errors:
warning: passing argument 1 of ‘setCreate’ from incompatible pointer
type
expected ‘copySetElements’ but argument is of type ‘struct location_t
* (*)(struct location_t *)’
Your example can be boiled down to
typedef void * (*VF)(void *);
typedef int * (*IF)(int *);
IF a = 0;
VF b = a; //Warning
In the C standard pointers to functions are less versatile than pointer to objects.
You can convert a pointer to an object to a pointer to void (and back) without any cast (and warnings) because there is clause in the standard that explicitly allows for it
A pointer to void may be converted to or from a pointer to any object type.
A pointer to
any object type may be converted to a pointer to void and back again;
the result shall
compare equal to the original pointer.
Note here that a function is not an object.
Regarding pointers to functions the only thing that the standard guarantees is:
A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer.
If a converted pointer is used to call a function whose type is not compatible with the referenced type, the behavior is undefined
Later the standard clarifies what does it mean for two functions to be compatible:
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.
Now you may think that void* and struct location_t* are compatible types, after all you can assign each others.
But the standard is crystal clear:
Two types have compatible type if their types are the same.
It later goes on with extending this relationship for more complex types and qualified types.
It may sound surprising but int* and void* are not compatible.
They can be assigned but are not compatible, after all the void* could point to a float or an object with different alignment requirements.
When it comes to pointers of different types the standard is concerned mostly about assigning them back and forth.
It doesn't forbid converting between incompatible pointer types, but using them is undefined behavior, hence the warning.
The better approach is to perform the cast inside the function, as suggested in this answer.
All your function should have signature void* (void*) so that no cast between pointers to functions is needed.
SetElement locationCopy(SetElement element)
{
Location location = (Location)element;
Location new_location = locationCreate(location->name);
return new_location;
}
The casts inside the functions are again subject to undefined behavior if the SetElement pointers are cast to incompatible pointers but that shouldn't happen if you'll call locationCopy only with pointers to Locations (as you indeed expect to do).
As a side note, some programmer may frown upon using a typedef to hide a pointer type.
I want to pass an int to a function that expects a char *.
The function header looks like this:
void swapEndian(char *pElement, unsigned int uNumBytes)
and calling the function with an int (I know, this won't work..)
int numLine;
swapEndian(numLine, sizeof(int));
So what must I do to numline to pass it to this function?
It sounds as though you just want:
swapEndian( (char*) &numLine, sizeof(int) );
The char* cast already suggested will work
swapEndian( (char*) &numLine, sizeof(int) );
...however if you can change swapEndian to take a void* it would be better since it avoids needing the cast (this is basically what void pointers are meant for) and also avoids any potential problems or potential future problems to do with the casting operation itself...
If swapEndian works with every pointers to object, then you can pass your pointer, because, according to the strict aliasing rule, a pointer to a character type can be dereference regardless to the type of the pointed object. You just need a typecast.
swapEndian((char *)&numLine, sizeof numLine);
C11 (n1570), § 6.5 Expressions
An object shall have its stored value accessed only by an lvalue
expression that has one of the following types:
— a type compatible with the effective type of the object,
— a qualified version of a type
compatible with the effective type of the object,
— a type that is the
signed or unsigned type corresponding to the effective type of the
object,
— a type that is the signed or unsigned type corresponding to
a qualified version of the effective type of the object,
— an
aggregate or union type that includes one of the aforementioned types
among its members (including, recursively, a member of a subaggregate
or contained union), or
— a character type.
In addition to other answers, assuming the swapEndian does what it name suggests, I believe it is wrongly declared. I suggest to declare it (if you can)
void swapEndian(void *pElement, size_t uNumBytes);
Is void a data type in the C programming language? If so, what type of values can it store? If we have int, float, char, etc., to store values, why is void needed? And what is the range of void?
Void is considered a data type (for organizational purposes), but it is basically a keyword to use as a placeholder where you would put a data type, to represent "no data".
Hence, you can declare a routine which does not return a value as:
void MyRoutine();
But, you cannot declare a variable like this:
void bad_variable;
However, when used as a pointer, then it has a different meaning:
void* vague_pointer;
This declares a pointer, but without specifying which data type it is pointing to.
Yes, void is a type. Whether it's a data type depends on how you define that term; the C standard doesn't.
The standard does define the term "object type". In C99 and earlier; void is not an object type; in C11, it is. In all versions of the standard, void is an incomplete type. What changed in C11 is that incomplete types are now a subset of object types; this is just a change in terminology. (The other kind of type is a function type.)
C99 6.2.6 paragraph 19 says:
The void type comprises an empty set of values; it is an incomplete
type that cannot be completed.
The C11 standard changes the wording slightly:
The void type comprises an empty set of values; it is an incomplete object type that
cannot be completed.
This reflects C11's change in the definition of "object type" to include incomplete types; it doesn't really change anything about the nature of type void.
The void keyword can also be used in some other contexts:
As the only parameter type in a function prototype, as in int func(void), it indicates that the function has no parameters. (C++ uses empty parentheses for this, but they mean something else in C.)
As the return type of a function, as in void func(int n), it indicates that the function returns no result.
void* is a pointer type that doesn't specify what it points to.
In principle, all of these uses refer to the type void, but you can also think of them as just special syntax that happens to use the same keyword.
The C Standard says that void is an incomplete type that cannot be completed (unlike other incomplete types that can be completed). This means you cannot apply the sizeof operator to void, but you can have a pointer to an incomplete type.