C: Generic ADT error when passing function pointers - c

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.

Related

C99: cast callbacks with different number of arguments

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.

(struct *) vs (void *) -- Funtion prototype equivalence in C11/C99

I was trying to implement GLOB_ALTDIRFUNC last night, and tripped into an interesting question.
While maybe slightly semantically different, are (void *) and (struct *) types equivalent?
Example code:
typedef struct __dirstream DIR;
struct dirent *readdir(DIR *);
DIR *opendir(const char *);
...
struct dirent *(*gl_readdir)(void *);
void *(*gl_opendir)(const char *);
...
gl_readdir = (struct dirent *(*)(void *))readdir;
gl_opendir = (void *(*)(const char *))opendir;
...
DIR *x = gl_opendir(".");
struct dirent *y = gl_readdir(x);
...
My intuition says so; they have basically the same storage/representation/alignment requirements; and they should be equivalent for arguments and return type.
Sections 6.2.5 (Types) and 6.7.6.3 (Function declarators (including prototypes)) of the c99 standard and the c11 standard seem to confirm this.
So the following implementation should in theory work:
https://www.openwall.com/lists/musl/2019/07/30/8
Now I see similar things being done in BSD and GNU libc code, which is interesting.
Are the equivalence of these conversions a result of an implementation artifact from the compilers, or it is a fundamental restriction/property that can be inferred from the standard's specification?
Does this result in undefined behavior?
#nwellnhof said:
For two pointer types to be compatible, both shall be identically
qualified and both shall be pointers to compatible types.
Ok, this is the key. How can (void *) and (struct *) be incompatible?
From 6.3.2.3 Pointers:
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 pointed-to type, the
behavior is undefined.
Not yet determined.
Further clarification:
Structs depend on their first element for alignment, so alignment requirements for a struct pointer should be the same as for void pointers, right?
I didn't initially specify DIR anywhere, but it's guaranteed to be a struct.
The whole point of the question is to know if I can avoid wrappers (like the one I did for gl_closedir, whose type is clearly incompatible).
While this may not be allowed by C11/C99, it is in practice used by BSD and GNU system code, so maybe some other relevant standard, e.g. POSIX, specifies the behavior.
Examples in the wild, in this very feature:
OpenBSD glob.c
glibc glob.h (ABI shouldn't change, controlled by _GNU_SOURCE -> __USE_GNU).
So, my thoughts so far are:
I can't think of any reason you can't exchange a (struct *) with a (void *), and the other way around.
A struct would have the alignment of the first element, and this could be a char so requirements of a pointer are exactly the same as for void pointers.
So a struct pointer should have the same implementation requirements as a void pointer, which is further reinforced by the requirement for all struct pointers to be equivalent.
So (const void *) should be equivalent to (const struct *)
and (void *) should be equivalent to (struct *)
and so on with all special attributes.
Considering ISO C alone: section 6.3.2.3 specifies which casts among pointer types are required not to lose information:
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.
A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined. Otherwise, when converted back again, the result shall compare equal to the original pointer.
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.
(emphasis mine) So, let's look at your code again, adding in some of the declarations from dirent.h:
struct dirent;
typedef /* opaque */ DIR;
extern struct dirent *readdir (DIR *);
struct dirent *(*gl_readdir)(void *);
gl_readdir = (struct dirent *(*)(void *))readdir;
DIR *x = /* ... */;
struct dirent *y = gl_readdir(x);
This casts a function pointer of type struct dirent *(*)(DIR *) to a function pointer of type struct dirent *(*)(void *) and then calls the converted pointer. Those two function pointer types are not compatible (in most cases, two types must be exactly the same to be "compatible"; there are a bunch of exceptions but none of them apply here) so the code has undefined behavior.
I want to emphasize that "they have basically the same storage/representation/alignment requirements" is NOT enough to avoid undefined behavior. The infamous sockaddr mess involves types with the same representation and alignment requirements, and even the same initial common subsequence, but struct sockaddr and struct sockaddr_in are still not compatible types, and reading the sa_family field of a struct sockaddr that was cast from a struct sockaddr_in is still undefined behavior.
In the general case, to avoid undefined behavior due to incompatible function pointer types, you have to write "glue" functions that convert back from void * to whatever concrete type is expected by the underlying procedure:
static struct dirent *
gl_readdir_glue (void *closure)
{
return readdir((DIR *)closure);
}
gl_readdir = gl_readdir_glue;
GLOB_ALTDIRFUNC is a GNU extension. Its specification was clearly (to me, anyway) written back in the days when nobody worried about the compiler optimizing based on the assumption that undefined behavior could never occur, so I do not think you should assume that the compiler will Do What You Mean with gl_readdir = (struct dirent *(*)(void *))readdir; If you are writing code that uses GLOB_ALTDIRFUNC, write the glue functions.
If you are implementing GLOB_ALTDIRFUNC, just store the void * you get from the gl_opendir hook in a variable of type void *, and pass it directly to the gl_readdir and gl_closedir hooks. Don't try to guess what the caller wants it to be.
EDIT: The code in the link is, in fact, an implementation of glob. What it does is reduce the non-GLOB_ALTDIRFUNC case to the GLOB_ALTDIRFUNC case by setting the hooks itself. And it doesn't have the glue functions I recommended, it has gl_readdir = (struct dirent *(*)(void *))readdir; I wouldn't have done it that way, but is true that this particular class of undefined behavior is unlikely to cause problems with the compilers and optimization levels that are typically used for C library implementations.
From the C99 standard, 6.7.5.1 Pointer declarators:
For two pointer types to be compatible, both shall be identically qualified and both shall be pointers to compatible types.
So void * and DIR * aren't compatible.
From 6.7.5.3 Function declarators (including prototypes):
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.
So struct dirent *(*)(void *) (the type of gl_readdir) nd struct dirent *(*)(DIR *) (the type of readdir) aren't compatible.
From 6.3.2.3 Pointers:
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 pointed-to type, the behavior is undefined.
So
gl_readdir = (struct dirent *(*)(void *))readdir;
gl_readdir(x);
is undefined behavior.
struct x* and struct y* for any two x and y are guaranteed to have the same representation and alignment requirements, same for union pointers, but not void pointers and struct pointers:
http://port70.net/~nsz/c/c11/n1570.html#6.2.5p28
A pointer to void shall have the same representation and alignment
requirements as a pointer to a character type.48) Similarly, pointers
to qualified or unqualified versions of compatible types shall have
the same representation and alignment requirements. All pointers to
structure types shall have the same representation and alignment
requirements as each other. All pointers to union types shall have the
same representation and alignment requirements as each other. Pointers
to other types need not have the same representation or alignment
requirements.
Furthermore, same representation and alignment requirements of a function type's "subtypes" isn't enough. For a call through a function pointer to be defined, the function pointer's target type must be compatible with the actual function's type and for function compatibility, strict compatibility between corresponding function arguments is required, which means that technically e.g., void foo(char*); is not compatible with void foo(char const*); even if char* and char const* have the same representation and alignment.
http://port70.net/~nsz/c/c11/n1570.html#6.7.6.3p15
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 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.)

Passing `int (*)(char const*)` where `int (*)(char*)` is expected

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.

Why have a "warning" when initializing an array of function pointers?

i try to initializing an array of function pointers and i have "warning":
ring-buffer.c:57:19: warning: assignment from incompatible pointer type [enabled by default]
RBufP->rbfunc[0] = &RBufPush;
^
but the neighborhood's ok:
/*typedef for func pointer*/
typedef RBRetCod_t (*RBFunc)();
/*RBufP*/
typedef struct {
RBufSiz_t size; /*size and mask*/
RBufDat_t rbufdat;
RBufPoint_t head, tail;
RBFunc rbfunc[3]; /*announce of function pointers array*/
} RBuf_t;
RBuf_t *RBufP;
...
/*init for func pointers array*/
RBufP->rbfunc[2] = &RBufDel; /*it is ok*/
RBufP->rbfunc[1] = &RBufPull; /*it is ok*/
RBufP->rbfunc[0] = &RBufPush; /*it is bad, why???*/
...
/*body of the functions*/
RBRetCod_t RBufPull(unsigned char *dat)
{
return RBSUCC;
}
RBRetCod_t RBufDel(void)
{
return RBSUCC;
}
RBRetCod_t RBufPush(unsigned char dat)
{
return RBSUCC;
}
please explain to me why the warning occurs in this line: RBufP->rbfunc[0] = &RBufPush;, but in adjacent rows are not there?
See C11 section 6.7.6.3 §14, specifying when 2 function types shall be considered compatible:
[...] 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. [...]
This is the case for RBufPull and RBufDel, but not RBufPush as unsigned char gets promoted to int.
If you called RBuPush through a pointer of type RBFunc, an int argument would get pushed to the stack, whereas RBufPush would expect an unsigned char. Depending on calling convention and endianness, you'll get incorrect results.
One solution is to change RBufPush to take an int argument. Another one is to use a cast, ie
RBufP->rbfunc[0] = (RBFunc)&RBufPush;
You'll need to cast back to the correct type RBRetCod_t (*)(unsigned char) before calling rbfunc[0].
From ISO/IEC:9899:
J.5.7 Function pointer casts
1 A pointer to an object or to void may be cast to a pointer to a function, allowing data to
be invoked as a function (6.5.4).
2 A pointer to a function may be cast to a pointer to an object or to void, allowing a
function to be inspected or modified (for example, by a debugger) (6.5.4).
But this rule is limited through:
6.3.2.3 Pointers
[...]
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 pointed-to type, the behavior is undefined.
So you are trying to acces different functions with the same Pointerobject type.
This is undefined behavior and the warning therefor is correct.
Note:
What it seems to me, what you try to achieve would be something like an interface in C#.
But this kind of functionallity is not available in C

Casting types of function while sending it as an argument to 'qsort'

typedef void* ListElement;
typedef int(*CompareListElements)(ListElement, ListElement);
ListResult listSort(List list, CompareListElements compareElement) {
.
.
.
qsort(arr, size, sizeof(*arr), compareElement);
.
.
.
}
The first two lines are to clarify what is the CompareListElements.
When I send compareElement as an argument to the library function 'qsort' I receive these warning messages:
passing argument 4 of 'qsort' from incompatible pointer type [enabled by default]
How can I solve this problem?
Avoid undefined behaviour
Rewrite the comparator so it matches what qsort() expects:
typedef int (*CompareListElements)(const void *, const void *);
And inside the comparator, do the conversion to the correct type:
int compare_elements(const void *v1, const void *v2)
{
const RealType *p1 = v1;
const RealType *p2 = v2;
…do comparison…
return …;
}
This way, you don't cast the function pointer at all. The RealType is whatever is the type hidden behind the (poorly chosen):
typedef void *ListElement;
The real type is not void; it is probably some structure type. Note that using void * as the list element type loses almost all the type safety that's available with C (which is arguably little enough to begin with). You'd do better with:
typedef struct Element ListElement;
or something like that, and passing pointers to ListElement around. (See also Is it a good idea to typedef pointers?. You may also find How to sort an array of structs in C? helpful, and there are undoubtedly other related questions that will help.).
Why does the cast lead to undefined behaviour?
Note that casting the function pointer leads to undefined behaviour according to the C standard:
C11 §6.3 Conversions — §6.3.2.3 Pointers ¶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.
So, while it is permissible to cast a function pointer that doesn't match what qsort() expects, the problem is that qsort() will invoke it as the converted type, and that is not generally compatible with the function's type, so the behaviour is undefined — notwithstanding examples to the contrary published by demigods in the Unix pantheon.
Often, you will get away with it, but the standard says you may not always get away with it. Since the fix is fairly simple, use it; avoid undefined behaviour.

Resources