Function pointer with a generic pointer parameter - c

I have an data structure that stores a generic void * for each node which gets casted to the correct type at the appropriate time. In the cleanup function for this object, I would then like to provide a callback so that this generic object can too be "cleaned up."
struct foo {
void *data;
// ...
};
void foo_cleanup(struct foo *foo, void (*data_cleanup)(void *data)) {
data_cleanup(foo->data);
// ...
}
// ...
void bar_cleanup(void *data) {
struct bar *bar = (struct bar *)data;
// ...
}
This works fine, however I would prefer if the signature of bar_cleanup referred to bar directly, rather than void *:
void bar_cleanup(struct bar *bar)
Of course, replacing that code as is creates "parameter type mismatch" warnings. Is there any way to achieve directly what I am trying to do, if not a similar method of achieving the same cleanup task?

What you are doing now is the correct way to deal with it. Your desire to use pointers to cleanup functions with a specific type runs foul of the (strict) rules in C11 (and C99, and probably C90 though I've not formally checked C90).
[§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.
Your existing code is:
struct foo {
void *data;
// ...
};
void foo_cleanup(struct foo *foo, void (*data_cleanup)(void *data)) {
data_cleanup(foo->data);
// ...
}
void bar_cleanup(void *data) {
struct bar *bar = (struct bar *)data;
// ...
}
This code is clean and obeys the rules. The pointer to the bar cleanup function has the signature void (*)(void *) which matches the pointer used by foo_cleanup(). The cast in bar_cleanup() is optional but explicit. Even if you omit the cast notation, that conversion will occur as C automatically converts from void * to struct bar *.
If you try to use the cleanup function:
void bar_cleanup(struct bar *bar);
you would have to make a call equivalent to:
struct foo foo37;
…code initializing foo37…
foo_cleanup(&foo37, (void (*)(void *))bar_cleanup);
This coerces the type of the function to a different pointer type. Unless the code inside foo_cleanup() knows somehow (how?) that the pointer needs to use function with the signature void (*)(struct bar *) and changes it before calling the cleanup function, it runs afoul of the rule in §6.3.2.3.
foo_cleanup(struct foo *foo, void (*data_cleanup)(void *data))
{
(*data_cleanup)(foo->data); // Undefined behaviour
if (data->…)
(*(void (*)(struct bar *))data_cleanup)(foo->data); // OK, but…
…
}
The unconditional call is wrong because the types of the real function pointer and the one claimed by the parameter type are different. The conditional call is clean because it casts the pointer back to its real type before calling the function. (This is C; the conversion from void * to struct bar * is automatic and valid.) . However, having to know what to convert the pointer-to-function to in the foo_cleanup() function defeats the purpose of using a pointer-to-function in the first place. It also isn't clear how foo_cleanup() does determine which cast is correct, and if you add a new type, you have to change the code again to support the new type.
All of this means that a solution using void bar_cleanup(struct bar *bar) is not really acceptable.
If you follow the strict rules laid down by the standard and still want to call void bar_cleanup(struct bar *), you have to write gruesome, non-maintainable, inflexible code.
If you want absolutely reliable code, you will follow these rules and keep your existing code (void bar_cleanup(void *data)). It has the beneficial side-effect of avoiding painful casts — function pointer casts are not pretty — and leaves the foo_cleanup() function unchanged regardless of how many different pointer types are stored in the data member of struct foo as long as the calling code knows which is the correct type (and if the calling code doesn't know, it's a case of "Abandon Hope All Ye Who Enter Here" anyway).
In practice, how serious a problem is this? Actually, you'll probably get away with it at the moment. But it is invoking undefined behaviour and compilers are ever eager to identify and exploit undefined behaviour to 'optimize' the code they generate. You can do as you want without the cast in foo_cleanup(), but you are
taking risks which can be simply and painlessly avoided by keeping your current code.
Note that this applies to comparator functions passed to qsort() or bsearch() in the standard library. Those functions should be written to take two const void * parameters and return an int. Doing otherwise runs foul of §6.3.2.3.
There are examples in otherwise respected C books that do not keep to these strict rules.

typedef void (*CLEANUP_FUNC)(void *);
void foo_cleanup(struct foo *foo, CLEANUP_FUNC *data_cleanup) {
data_cleanup(foo->data);
// ...
}
void bar_cleanup(struct bar *data) {
// ...
}
foo_cleanup(foo, (CLEANUP_FUNC *)bar_cleanup);
The following is a full example featuring generic stack Stack and some elements of type Foo.
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
// ---
typedef void (STACK_DEALLOCATOR)(void *);
typedef struct Stack {
STACK_DEALLOCATOR *element_deallocator;
size_t num_elements;
size_t num_allocated;
void **elements;
} Stack;
Stack *Stack_New(STACK_DEALLOCATOR *element_deallocator) {
Stack *this = malloc(sizeof(Stack));
if (this == NULL)
goto ERROR;
this->element_deallocator = element_deallocator;
this->num_elements = 0;
this->num_allocated = 4;
this->elements = malloc(sizeof(void *) * this->num_allocated);
if (this->elements == NULL)
goto ERROR2;
return this;
ERROR2: free(this);
ERROR: return NULL;
}
int Stack_Push(Stack *this, void *element) {
if (this->num_elements == this->num_allocated) {
// ...
}
this->elements[ this->num_elements++ ] = element;
return 1;
}
void Stack_Destroy(Stack *this) {
void **element = this->elements;
for (size_t i=this->num_elements; i--; ) {
this->element_deallocator(*(element++));
}
free(this->elements);
free(this);
}
// ---
typedef struct Foo {
int data;
// ....
} Foo;
Foo *Foo_New(int data) {
Foo *this = malloc(sizeof(Foo));
if (this == NULL)
return NULL;
this->data = data;
return this;
}
void Foo_Destroy(Foo *this) {
free(this);
}
// ---
int main(void) {
Stack *stack = Stack_New((STACK_DEALLOCATOR *)Foo_Destroy);
if (stack == NULL) {
perror("Stack_New");
goto ERROR;
}
Foo *foo = Foo_New(123);
if (foo == NULL) {
perror("Foo_New");
goto ERROR2;
}
if (!Stack_Push(stack, foo)) {
perror("Stack_Push");
goto ERROR3;
}
Stack_Destroy(stack);
return 0;
ERROR3: Foo_Destroy(foo);
ERROR2: Stack_Destroy(stack);
ERROR: return 1;
}

Related

How can I correctly assign a pointer to a call back function?

I try to call a function in a IRQ with C, with the next code I get it.
static void (*functionPulsacion)();
void eint2_init(void *funcPulsacion){
functionPulsacion = funcPulsacion;
}
But when I compile in Keil the IDE show me the next message:
Button2.c(38): warning: #513-D: a value of type "void *" cannot be assigned to an entity of type "void (*)()"
What is the good way for do this?.
Thank you in advance
Make sure the type of funcPulsacion matches that of functionPulsacion, like so:
static void (*functionPulsacion)(void);
void eint2_init(void (*funcPulsacion)(void)) {
functionPulsacion = funcPulsacion;
}
It helps to use typedef to define the function pointer type so it can be reused:
typedef void (*functionPulsacion_type)(void);
static void functionPulsacion_type functionPulsacion;
void eint2_init(functionPulsacion_type funcPulsacion) {
functionPulsacion = funcPulsacion;
}
It's the same syntax for the parameter variable as for the static one.
static void (*functionPulsacion)(void);
void eint2_init(void (*funcPulsacion)(void)) {
functionPulsacion = funcPulsacion;
}
Typedefs make function pointers a lot more readable, though.
typedef void (*PulsacionFunc)(void);
static PulsacionFunc pulsacion_func;
void eint2_init(PulsacionFunc a_pulsacion_func) {
pulsacion_func = a_pulsacion_func;
}
This is the type of a pointer to a variadic function that returns void.
static void (*functionPulsacion)();
The argument to this function, however, is void *
void eint2_init(void *funcPulsacion){
functionPulsacion = funcPulsacion;
}
Funny thing is, with any data pointer, you could assign a void * without any issues
void foo(void *vp)
{
int *ip = vp;
}
void bar(void *vp)
{
char *cp = vp;
}
but according to the standard, not to a function pointer
void nope(void *vp)
{
void (*fp)() = vp; // this might be a problem!
}
Any data pointer can be cast to a void * and back again, without even a warning.
But that is data pointers; not function pointers. I belive that POSIX says that
you can assign between void * and function pointers, but the C standard does not.
You can cast between function pointers, though.
Of course, that is dangerous, because once you call the function it doesn't care what
type you think it has; it will do its thing according to what type it knows it has.
So, it is best just to use the right pointer type here.
void yeah(void (*arg)())
{
void (*fp)() = arg;
}

assignment to 'int' from 'void *' makes integer from pointer without a cast

I am trying to make a linked list stack, and I followed a tutorial online, however I get this warning, and the author does not.
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
typedef struct stack {
int data;
struct stack *next;
} stack;
stack *top;
void Initialize(stack *s){
s=malloc(sizeof(stack));
s->data=NULL;
s->next=NULL;
top=s;
return;
}
During debugging I get a segmentation fault, if I don't use malloc in the initialize function.
MINGW-W64 compiler.
The warning is due to the fact the the NULL macro is defined (by most modern compilers) as ((void *)0), as it is intended to be used only for pointers. Assigning this value to the data member of your structure causes the warning.
To remove this warning, use s->data=0; in place of s->data=NULL;. The author of the tutorial is either using an older compiler or has disabled this particular warning.
Also, the pointer (s) passed to your Initialize function will be a copy of whatever variable you use as an argument when you call that function and, as such, its value is not updated in the calling code. You haven't specified how you intend to use that function, but here is a (perhaps) better implementation:
stack* Initialize(void) { // Don't need an argument - just return the 'answer'
stack* made = malloc(sizeof(stack));
if (made) { // Don't attempt the initialization if "malloc" failed!
made->data = 0;
made->next = NULL;
}
return made;
}
And then, when you call that function, you can assign its returned value to your 'global' top pointer:
//...
top = Initialize();
// You should check that 'top' is not NULL before continuing!
In C the macro NULL is defined like
( void * )0
that is the type of the expression is a null pointer of the type void *.
Thus in this statement
s->data=NULL;
a pointer is assigned to an object of the type int and the compiler issues a message saying that you are doing something wrong.
The function Initialize as it is written does not make a sense. Apart of all for example this statement
s=malloc(sizeof(stack));
does not change the original pointer top used as the function argument.
In fact the function is redundant.
Nevertheless if to write such a function then it can look either like
stack * Initialize( void )
{
return NULL;
}
and called like
stack *top = Initialize();
or like
void Initialize( stack **top )
{
*top = NULL;
}
Or for a new created node it can be declared and defined like
void Initialize( stack *s, int data, stack *next )
{
s->data = data;
s->next = next;
}

Checking if it's safe to cast a function pointer into another one

In my code I'm trying to use dummy objects to perform modularity in C.
At the moment I specify important function useful for every objects via function pointers, like destructors, toString, equals as follows:
typedef void (*destructor)(const void* obj);
typedef void (*to_string)(void* obj, int bufferSize, const char* buffer);
typedef bool (*equals)(void* obj, const void* context);
In my code base then I use function pointer compatible to the given typedef to abstractly handle objects, for example:
struct Foo {
int a;
} Foo;
void destroyFoo1(const Foo* p) {
free((void*)p);
}
int main() {
//...
Foo* object_to_remove_from_heap = //instance of foo
destructor d = destroyFoo1;
//somewhere else
d(object_to_remove_from_heap, context);
}
The code compiles and normally it would generate only a warning (destructor first parameter should be a const void* but instead it is a const Foo*).
However,
since I've enabled -Werror, the "invalid pointer cast" is treated as an error.
To solve this issue, I need to cast the function pointer, as follows:
destructor d = (destructor)destroyFoo1;
I know per standard const void* and const Foo* may have different memory size, but I assume the platform where the code is deployed const void* and const Foo* are allocated in the same memory space and have the same size. In general I assume the cast of function pointer where at least one pointer argument is changed into some other pointer is a safe casting.
This is all good but the approach shows its weakness when, for example, I need to change the signature of destructor type, for example by adding a new const void* context parameter. Now the interesing warning is silenced and the number of parameters in the function pointer call mismatch:
//now destructor is
typedef void (*destructor)(const void* obj, const void* context);
void destroyFoo1(const Foo* p) {
free((void*)p);
}
destructor d = (destructor)destroyFoo1; //SILCENCED ERROR!!destroyFoo1 has invalid parameters number!!!!
//somewhere else
d(object_to_remove_from_heap, context); //may mess the stack
My question is: is there a way to check if a function pointer can indeed be safely casted into another (and generating a compile error if not)?, something like:
destructor d = CHECK_IF_FUNCTION_RETURNS_VOID_AND_REQUIRE_2_VOID_POINTERS(destroyFoo1);
Something that if we pass destroyFoo1 everything is fine but if we pass destroyFoo2 the compiler complains.
Below a code that summarizes the problem
typedef void (*destructor)(const void* obj, const void* context);
typedef struct Foo {
int a;
} Foo;
void destroyFoo1(const Foo* p, const void* context) {
free((void*)p);
if (*((int*)context) == 0) {
printf("hello world\n");
}
}
void destroyFoo2(const Foo* p) {
free((void*)p);
}
int main() {
//this is(in my case) safe
destructor destructor = (destructor) destroyFoo1;
//this is really a severe error!
//destructor destructor = (destructor) destroyFoo2;
Foo* a = (Foo*) malloc(sizeof(Foo));
a->a = 3;
int context = 5;
if (a != NULL) {
//call a destructor: if destructor is destroyFoo2 this is a SEVERE ERROR!
//calling a function accepting a single parameter with 2 parameters!
destructor(a, &context);
}
}
Thanks for any kind of reply
Ok, I think I've figure it out, but it's not straightforward.
First of all the problem is that CHECK_IF_FUNCTION_RETURNS_VOID_AND_REQUIRE_2_VOID_POINTERS needs to compile-time compare 2 signatures: an input one (given from an input function pointer, e.g., destroyFoo1) and a base one (i.e., signature of destructor type): if we implement a method that does that, we can check if 2 signatures are "compliant" or not.
We do that by exploiting C preprocessor. The main idea is that each function we would like to use as destructor has a macro defined. CHECK_IF_FUNCTION_RETURNS_VOID_AND_REQUIRE_2_VOID_POINTERS will be a macro as well that simply generates a macro name based on the type signature of destructor: if the macro name generated in CHECK_IF_FUNCTION_RETURNS_VOID_AND_REQUIRE_2_VOID_POINTERS exists, than we assume the functionPointer is compliant with destructor and we cast to it. We throw a compilation error otherwise. Since we need a macro definition per function we want to use as destructor, this may be a costly solution in huge codebases.
Note: The implementation is GCC dependent(it uses a variant of ## and _Pragma, but I think it can be easily ported to some other compilers as well).
So, for example:
#define FUNCTION_POINTER_destructor_void_destroyFoo1_voidConstPtr_voidConstPtr 1
void destroyFoo1(const Foo* p, const void* context);
The macro value is just a constant number. What is important is the name of the macro which has a well defined structure. The convention you use is irrelevant, just choose and stick with one. Here I've used the following convention:
//macro (1)
"FUNCTION_POINTER_" typdefName "_" returnType "_" functionName "_" typeparam1 "_" typeparam2 ...
Now we're going to define a macro that checks if 2 signatures are the same. To help us, we're using P99 project. We are going to use few macros from the project, so you can implement such macros on your own if you don't want to rely on it:
#define CHECK_IF_FUNCTION_RETURNS_VOID_AND_REQUIRE_2_VOID_POINTERS(functionName) \
_ENSURE_FUNCTION_POINTER(1, destructor, void, functionName, voidConstPtr, voidConstPtr)
#define _ENSURE_FUNCTION_POINTER(valueToCheck, castTo, expectedReturnValue, functionName, ...) \
P99_IF_EQ(valueToCheck, _GET_FUNCTION_POINTER_MACRO(castTo, expectedReturnValue, functionName, ## __VA_ARGS__)) \
((castTo)(functionName)) \
(COMPILE_ERROR())
#define COMPILE_ERROR() _Pragma("GCC error \"function pointer casting error!\"")
The input of the macro is the macro value of (1) to check (i.e., 1 in this case, the value from the function macro), the typedef we want to check against (castTo), the return type we expect functionName to have and the functionName the user passed to CHECK_IF_FUNCTION_RETURNS_VOID_AND_REQUIRE_2_VOID_POINTERS (e.g., destroyFoo1 or destroyFoo2). The variadic are the types of each parameters.These parameters needs to be same as in (1): we write voidConstPtr because we cannote have const void* within a macro name.
_GET_FUNCTION_POINTER_MACRO generates the macro associated to the signature we expect functionName to have:
#define _DEFINE_FUNCTION_POINTER_OP(CONTEXT, INDEX, CURRENT, NEXT) P99_PASTE(CURRENT, NEXT)
#define _DEFINE_FUNCTION_POINTER_FUNC(CONTEXT, CURRENT, INDEX) P99_PASTE(_, CURRENT)
#define _GET_FUNCTION_POINTER_MACRO(functionPointerType, returnValue, functionName, ...) \
P99_PASTE(FUNCTION_POINTER, _, functionPointerType, _, returnValue, _, functionName, P99_FOR(, P99_NARG(__VA_ARGS__), _DEFINE_FUNCTION_POINTER_OP, _DEFINE_FUNCTION_POINTER_FUNC, ## __VA_ARGS__))
//example
_GET_FUNCTION_POINTER_MACRO(destructor, void, destroyFoo2, voidConstPtr, voidConstPtr)
//it generates
FUNCTION_POINTER_destructor_void_destroyFoo2_voidConstPtr_voidConstPtr
So, for example:
#define FUNCTION_POINTER_destructor_void_destroyFoo1_voidConstPtr_voidConstPtr 1
void destroyFoo1(const Foo* p, const void* context)
{
free((void*)p);
if (*((int*)context) == 0) {
printf("hello world\n");
}
}
void destroyFoo2(const Foo* p)
{
free((void*)p);
}
int main(void)
{
//this will work:
//FUNCTION_POINTER_destructor_void_destroyFoo1_voidConstPtr_voidConstPtr
//macro exist and is equal to 1
destructor destructor1 = CHECK_IF_FUNCTION_RETURNS_VOID_AND_REQUIRE_2_VOID_POINTERS(destroyFoo1);
//this raise a compile error:
//FUNCTION_POINTER_destructor_void_destroyFoo2_voidConstPtr_voidConstPtr
//does not exist (or exists but its value is not 1)
destructor destructor2 = CHECK_IF_FUNCTION_RETURNS_VOID_AND_REQUIRE_2_VOID_POINTERS(destroyFoo2);
}
Important Notes
actually voidConstPtr or even void in the macro name are just strings. Everything would have worked even if you replaced void with helloWorld. They just follow a convention.
The last bit of understanding is the condition implemented by P99_IF_EQ in _ENSURE_FUNCTION_POINTER: if the output of _GET_FUNCTION_POINTER_MACRO is an existing macro, the preprocessor will automatically replace it with its value, otherwise the macro name will remain the same; if the macro is replaced with 1 (macro generated _GET_FUNCTION_POINTER_MACRO existing and equal to 1) we will assume the only one this is achieved is because the developer defined macro (1) and we will assume functionName complies with destructor. We will throw a compile time error otherwise.
It's been a while, but shouldn't the code for function pointer assignment be:
//this is okay
destructor destructor1 = &destructorFoo1;
//this should throw a compilation error!
destructor destructor2 = &destructorFoo2;
EDIT:
Okay I've gone away and had a closer look at this.
If I change the declaration of the function pointer to use const Foo* p rather than const void* obj so that we're not relying on a cast to hide the incompatibility between void* and Foo* then I get a warning with default compiler settings.
Then by casting destroyFoo2 to (destructor) you then hide this warning, by forcing the compiler to treat the function as that type.
I guess that this highlights the pitfalls of casting.
I checked this using the following code:
typedef struct Foo
{
int a;
} Foo;
typedef void (*destructor)(const Foo* p, const void* context);
void destroyFoo1(const Foo* p, const void* context);
void destroyFoo1(const Foo* p, const void* context)
{
free((void*)p);
if (*((int*)context) == 0) {
printf("hello world\n");
}
}
void destroyFoo2(const Foo* p);
void destroyFoo2(const Foo* p)
{
free((void*)p);
}
int main(void)
{
//this is okay
destructor destructor1 = destroyFoo1;
//this triggers a warning
destructor destructor2 = destroyFoo2;
//This doesn't generate a warning
destructor destructor3 = (destructor)destroyFoo2;
}

function prototype with void* parameter

I have two functions, each taking a pointer to a different type:
void processA(A *);
void processB(B *);
Is there a function pointer type that would be able to hold a pointer to either function without casting?
I tried to use
typedef void(*processor_t)(void*);
processor_t Ps[] = {processA, processB};
but it didn't work (compiler complains about incompatible pointer initialization).
Edit: Another part of code would iterate through the entries of Ps, without knowing the types. This code would be passing a char* as a parameter. Like this:
Ps[i](data_pointers[j]);
Edit: Thanks everyone. In the end, I will probably use something like this:
void processA(void*);
void processB(void*);
typedef void(*processor_t)(void*);
processor_t Ps[] = {processA, processB};
...
void processA(void *arg)
{
A *data = arg;
...
}
If you typedef void (*processor_t)(); then this will compile in C. This is because an empty argument list leaves the number and types of arguments to a function unspecified, so this typedef just defines a type which is "pointer to function returning void, taking an unspecified number of arguments of unspecified type."
Edit: Incidentally, you don't need the ampersands in front of the function names in the initializer list. In C, a function name in that context decays to a pointer to the function.
It works if you cast them
processor_t Ps[] = {(processor_t)processA, (processor_t)processB};
By the way, if your code is ridden with this type of things and switch's all over the place to figure out which function you need to call, you might want to take a look at object oriented programming. I personally don't like it much (especially C++...), but it does make a good job removing this kind of code with virtual inheritance.
This can be done without casts by using a union:
typedef struct A A;
typedef struct B B;
void processA(A *);
void processB(B *);
typedef union { void (*A)(A *); void (*B)(B *); } U;
U Ps[] = { {.A = processA}, {.B = processB} };
int main(void)
{
Ps[0].A(0); // 0 used for example; normally you would supply a pointer to an A.
Ps[1].B(0); // 0 used for example; normally you would supply a pointer to a B.
return 0;
}
You must call the function using the correct member name; this method only allows you to store one pointer or the other in each array element, not to perform weird function aliasing.
Another alternative is to use proxy functions that do have the type needed when calling with a parameter that is a pointer to char and that call the actual function with its proper type:
typedef struct A A;
typedef struct B B;
void processA(A *);
void processB(B *);
typedef void (*processor_t)();
void processAproxy(char *A) { processA(A); }
void processBproxy(char *B) { processB(B); }
processor_t Ps[] = { processAproxy, processBproxy };
int main(void)
{
char *a = (char *) address of some A object;
char *b = (char *) address of some B object;
Ps[0](a);
Ps[1](b);
return 0;
}
I used char * above since you stated you are using it, but I would generally prefer void *.

function pointer in C, with or without *?

is there difference between with or without * for function pointer in C?
my function pointer declaration like this
typedef void (*DListVisitNode) (Node*, void*);
void DListTraverse( NodeList* , DListVisitNode , void*);
i have code like these
void print_index( Node* node, void* ctx)
{
printf("index:%d\n", node->index);
}
void* print_content( Node* node, void* ctx)
{
printf("content:%s\n", node->content);
}
void DListTraverse(NodeList* nodelist, DListVisitNode visit_func, void* ctx)
{
Node* cur_node = nodelist->headnode;
while( cur_node != NULL)
{
visit_func( cur_node, ctx );
cur_node = cur_node->nextnode;
}
}
DListTraverse( nodelist, print_content, NULL );
DListTraverse( nodelist, print_index, NULL );
both of DListTraverse works, but the one with * throws warning like this
warning: passing argument 2 of ‘DListTraverse’ from incompatible pointer type
i would simply delete the * afterward, but what's the difference between them?
print_content is defined as returning a void* i.e. a generic raw pointer.
print_index is defined as returning void i.e. without any results.
These are different signatures. Only print_index matches DListVisitNode.
My coding style is to define signature thru typedef like
typedef void signature_t (int);
Notice that no pointer is involved above. This names the signature of functions with one int argument and no results.
then, when needing pointers to such functions of above signature, use signature_t*
What is true is that the name of a function is like the name of an array; the language implicitly convert these to pointers. So DListTraverse(nodelist, print_content, NULL) is understood like DListTraverse(nodelist, &print_content, NULL)
You should enable all warnings on your compiler; with gcc that means giving -Wall -Wextra as program arguments to the compiler.
You've declared print_content as returning a void * (a pointer), which means it doesn't match DListVisitNode. However, as the function doesn't actually return anything (no return statement), you should be gatting another warning about that.
You may be confused about the difference between the following to typedefs:
typedef void (*DListVisitNode) (Node*, void*);
typedef void * (*DListVisitNode) (Node*, void*);
Or equivalently, between the following two types:
void (*) (Node *, void *)
void * (*) (Node *, void *)
The former is a pointer to a function returning void, the latter is a pointer to a function returning void *. Each of your print functions is an example of one such function.
Naturally, function pointers of different types are incompatible and not implicitly convertible, as surely this would make no sense: You can't just pretend that a function actually has a completely different signature and expect to be able to call it in any meaningful way. It would be like pretending that a bicycle is a car and then trying to refuel it at a gas station.
typedef void (*DListVisitNode) (Node*, void*);
Defines a pointer to an function as an type which takes two parameters Node * and void * and returns a void.
Once you use the above statement, DListVisitNode can be used as an type, the exact type is as mentioned above.
void* print_content( Node* node, void* ctx)
returns a void * and not a void.
C is a strongly typed language, the c standard mandates that any type violations must be reported by the compiler, hence there is a type mismatch and the compiler reports it to you. Basically, if your function doesn't return anything use the return type as void or if you intend to return a particular type then use that specific type as an return type.

Resources