B: because every function must have a return type
*including function pointers*?
C: what about ty[p]edef?
i have:
void moo(char HISTORY[][64], int hst_len, int invalid, const char *gme_msg)
void mastermind(char HISTORY[][64], int hst_len, int invalid, const char *gme_msg)
to use either i have:
void (*render)(char [][64], int, int, const char *) = MOO ? moo : mastermind;
my multi-part question:
why is void required for the function pointer?
A: because both moo() & mastermind return void?
B: because every function must have a return type
*including function pointers*?
C: what about tyedef?
On Fri, 2 Jan 2026 07:24:55 -0000 (UTC), Michael Sanders wrote:
B: because every function must have a return type
*including function pointers*?
Yes. The type of the function pointer is of course that of the type of functions it points to.
C: what about ty[p]edef?
The same rule applies as with any typedef: put the name to be given to
the type in the place where the variable you might be declaring of
that type would go.
Michael Sanders <porkchop@invalid.foo> writes:
i have:
void moo(char HISTORY[][64], int hst_len, int invalid, const char *gme_msg) >>
void mastermind(char HISTORY[][64], int hst_len, int invalid, const char *gme_msg)
to use either i have:
void (*render)(char [][64], int, int, const char *) = MOO ? moo : mastermind;
my multi-part question:
why is void required for the function pointer?
Every function has a type. Every function pointer has a type. For a function pointer to point to a function, it must have type "pointer to
blah", where "blah" is the type of the function.
A function type includes information about the type of the function's
result and the types of its parameters. The type of both foo and
mastermind can be written as:
void(char[]64], int, int, const char*)
A: because both moo() & mastermind return void?
Yes.
B: because every function must have a return type
*including function pointers*?
Functions and function pointers are of course distinct. A function is
of function type. A function pointer is of an object type, specifically
a pointer-to-function type.
Every pointer-to-function type is derived from a function type, and that function type specifies the type returned by the function -- "void" in
this case.
C: what about tyedef?
What about it?
You can define a typedef for the function type, which could make the
code a bit simpler. (It's also common to define a typedef for a pointer-to-function type, but I prefer to typedef the function type
itself.)
An example based on your code :
```
#include <stdio.h>
void moo(char HISTORY[][64], int hst_len, int invalid, const char *gme_msg) {
puts("moo");
}
void mastermind(char HISTORY[][64], int hst_len, int invalid, const char *gme_msg) {
puts("mastermind");
}
typedef void functype(char [][64], int, int, const char *);
int main(void) {
for (int MOO = 0; MOO < 2; MOO ++) {
functype *render = MOO ? moo : mastermind;
printf("MOO is %s: ", MOO ? "true" : "false");
render(NULL, 0, 0, "");
}
}
```
The output :
```
MOO is false: mastermind
MOO is true: moo
```
(Incidentally, all-caps identifiers are most commonly used as macro
names, with FILE being a notable exception.)
C: what about ty[p]edef?
(It's also common to define a typedef for a
pointer-to-function type, but I prefer to typedef the function type
itself
On 1/2/26 3:43 PM, Michael Sanders wrote:
(It's also common to define a typedef for a
pointer-to-function type, but I prefer to typedef the function type
itself
OK, that's crazy! I didn't know of this!
Thanks for sharing!
B: because every function must have a return type
*including function pointers*?
C: what about tyedef?
i have:
void moo(char HISTORY[][64], int hst_len, int invalid, const char
*gme_msg)
void mastermind(char HISTORY[][64], int hst_len, int invalid, const
char *gme_msg)
to use either i have:
void (*render)(char [][64], int, int, const char *) = MOO ? moo :
mastermind;
my multi-part question:
why is void required for the function pointer?
A: because both moo() & mastermind return void?
B: because every function must have a return type
*including function pointers*?
C: what about tyedef?
i have:
void moo(char HISTORY[][64], int hst_len, int invalid, const char *gme_msg)
void mastermind(char HISTORY[][64], int hst_len, int invalid, const char *gme_msg)
to use either i have:
void (*render)(char [][64], int, int, const char *) = MOO ? moo : mastermind;
my multi-part question:
why is void required for the function pointer?
A: because both moo() & mastermind return void?
B: because every function must have a return type
*including function pointers*?
C: what about tyedef?
On 2026-01-02, Michael Sanders <porkchop@invalid.foo> wrote:
B: because every function must have a return type
*including function pointers*?
What it is you think type is, in the context of C?
Does type survive into run-time?
If a function pointer is missing type information about return type, and
that function pointer is needed for expressing a function call, where
does the compiler get the type from?
On 2026-01-02 02:24, Michael Sanders wrote:
i have:
void moo(char HISTORY[][64], int hst_len, int invalid, const char
*gme_msg)
void mastermind(char HISTORY[][64], int hst_len, int invalid, const
char *gme_msg)
to use either i have:
void (*render)(char [][64], int, int, const char *) = MOO ? moo :
mastermind;
my multi-part question:
why is void required for the function pointer?
Because otherwise you would have to cast each function to match the type
of the function pointer before assigning it.
A: because both moo() & mastermind return void?
You can safely convert any function pointer to any other function
pointer type. However, you must convert it back to a a pointer to a
function type that is compatible with the function itself before using
the pointer to call that function.
The simplest way to deal with that fact is to make sure that all the functions that you might want the pointer to point at have the same
type, and then just declare it as a pointer to that type.
The more complicated approach requires you to keep track in some fashion
what the original function type was, and to convert it back to that type before dereferencing it. There are rare situations where this is worth
doing, but in general you should avoid this approach.
B: because every function must have a return type
*including function pointers*?
Well, that's true, but what I said about compatibility of a function
pointer with a function's actual type is the more important issue, and implies what you've said above.
C: what about tyedef?
You'll need to explain what you your question about typedef is. Here's
the key thing you need to know: a typedef does not declare a new type,
it merely provides an alias for a type. If you replace every occurrence
of a typedef with the type it's a defined as an alias of, your code
should work exactly the same.
If you have any function that returns type T (including when T is void[...]
like your examples), then its type is 'function(...)returning T'.
A pointer to such a function will have a type:
'pointer to function(...)returning T'.
On 02/01/2026 07:24, Michael Sanders wrote:
i have:
void moo(char HISTORY[][64], int hst_len, int invalid, const char *gme_msg) >>
void mastermind(char HISTORY[][64], int hst_len, int invalid, const char *gme_msg)
to use either i have:
void (*render)(char [][64], int, int, const char *) = MOO ? moo : mastermind;
my multi-part question:
why is void required for the function pointer?
A: because both moo() & mastermind return void?
Neither return anything. But in C there is no special syntax to denote routines that don't return values. So a dummy 'void' return type is used.
B: because every function must have a return type
*including function pointers*?
If you have any function that returns type T (including when T is void
like your examples), then its type is 'function(...)returning T'.
A pointer to such a function will have a type:
'pointer to function(...)returning T'.
In C syntax, that return type always goes at the start of the
declaration. In your examples, that will be 'void'.
C: what about tyedef?
Typedefs create convenient aliases for types. So if you created an alias
U for that pointer type in my last example, then you can subsequently
just use U:
U p, q, r;
This declares three variable all with type 'pointer to ... void'. So in
this case the details are hidden, including that void.
On Fri, 2 Jan 2026 17:48:16 -0000 (UTC), Kaz Kylheku wrote:
On 2026-01-02, Michael Sanders <porkchop@invalid.foo> wrote:
B: because every function must have a return type
*including function pointers*?
What it is you think type is, in the context of C?
Does type survive into run-time?
If a function pointer is missing type information about return type, and
that function pointer is needed for expressing a function call, where
does the compiler get the type from?
Its void that's throwing me Kaz. I'm not sure what to think when
it comes to void pointers.
A void pointer (the pointer itself) is not of the type
it points to no? Its typeless & unknown at compile time
A 'normal' pointer in C is a variable that stores the address
of another variable.
And *is of the type it points to*...
char str[] = "learning publicly is a humbling experience.";
char *ptr = str;
Then void enters stage left...
void *genericPointer;
It can point to an integer, a character, a structure, or any other type.
void *genericPointer = &intValue;
void *genericPointer = str[];
And it seems to survive a lack of type at compile time.
Man I'm confused on this. I mean I get void is flexible,
but 'void' is not void in any sense... its like a mask in some
way (I'm groping for a definition) I need to study void more.
On Fri, 2 Jan 2026 17:48:16 -0000 (UTC), Kaz Kylheku wrote:
On 2026-01-02, Michael Sanders <porkchop@invalid.foo> wrote:
B: because every function must have a return type
*including function pointers*?
What it is you think type is, in the context of C?
Does type survive into run-time?
If a function pointer is missing type information about return type, and
that function pointer is needed for expressing a function call, where
does the compiler get the type from?
Its void that's throwing me Kaz. I'm not sure what to think when
it comes to void pointers.
A void pointer (the pointer itself) is not of the type
it points to no? Its typeless & unknown at compile time
A 'normal' pointer in C is a variable that stores the address
of another variable. And *is of the type it points to*...
char str[] = "learning publicly is a humbling experience.";
char *ptr = str;
Then void enters stage left...
void *genericPointer;
It can point to an integer, a character, a structure, or any other type.
void *genericPointer = &intValue;
void *genericPointer = str[];
And it seems to survive a lack of type at compile time.
Man I'm confused on this. I mean I get void is flexible,
but 'void' is not void in any sense... its like a mask in some
way (I'm groping for a definition) I need to study void more
On Fri, 2 Jan 2026 17:48:16 -0000 (UTC), Kaz Kylheku wrote:
On 2026-01-02, Michael Sanders <porkchop@invalid.foo> wrote:
B: because every function must have a return type
*including function pointers*?
What it is you think type is, in the context of C?
Does type survive into run-time?
If a function pointer is missing type information about return type, and
that function pointer is needed for expressing a function call, where
does the compiler get the type from?
Its void that's throwing me Kaz. I'm not sure what to think when
it comes to void pointers.
A void pointer (the pointer itself) is not of the type
it points to no? Its typeless & unknown at compile time
A 'normal' pointer in C is a variable that stores the address
of another variable. And *is of the type it points to*...
char str[] = "learning publicly is a humbling experience.";
char *ptr = str;
Then void enters stage left...
void *genericPointer;
It can point to an integer, a character, a structure, or any other type.
void *genericPointer = &intValue;
void *genericPointer = str[];
And it seems to survive a lack of type at compile time.
Man I'm confused on this. I mean I get void is flexible,
but 'void' is not void in any sense... its like a mask in some
way (I'm groping for a definition) I need to study void more
On 2026-01-02, Michael Sanders <porkchop@invalid.foo> wrote:
On Fri, 2 Jan 2026 17:48:16 -0000 (UTC), Kaz Kylheku wrote:
On 2026-01-02, Michael Sanders <porkchop@invalid.foo> wrote:
B: because every function must have a return type
*including function pointers*?
What it is you think type is, in the context of C?
Does type survive into run-time?
If a function pointer is missing type information about return type, and >>> that function pointer is needed for expressing a function call, where
does the compiler get the type from?
Its void that's throwing me Kaz. I'm not sure what to think when
it comes to void pointers.
Because you teleported here from 1985.
On 1/2/26 3:43 PM, Michael Sanders wrote:
(It's also common to define a typedef for a
pointer-to-function type, but I prefer to typedef the function type
itself
OK, that's crazy! I didn't know of this!
It seems a bit weird at first, but I like the type consistency:
you declare `typedef int callback(int)` and declare `callback *c`
making it very explicit that we are talking of a pointer.
On the other hand it is not clear what `callback c` should do,
but it makes sense as soon as you declare it.
The comp.lang.c FAQ <https://www.c-faq.com/> has some good information, though some of it is a bit dated. Section 4 covers pointers, and
section 6 covers arrays and pointers.
Because you teleported here from 1985.
On 1/2/2026 1:52 PM, Kaz Kylheku wrote:
On 2026-01-02, Michael Sanders <porkchop@invalid.foo> wrote:
On Fri, 2 Jan 2026 17:48:16 -0000 (UTC), Kaz Kylheku wrote:
On 2026-01-02, Michael Sanders <porkchop@invalid.foo> wrote:
B: because every function must have a return type
-a-a-a *including function pointers*?
What it is you think type is, in the context of C?
Does type survive into run-time?
If a function pointer is missing type information about return type,
and
that function pointer is needed for expressing a function call, where
does the compiler get the type from?
Its void that's throwing me Kaz. I'm not sure what to think when
it comes to void pointers.
Because you teleported here from 1985.
[...]
One note, void* cannot hold a function pointer without getting undefined
or implementation-defined behavior.
typedef void (*generic_func_ptr)(void)
A pattern I've used more than once when setting up a table of function pointers that act like op-codes. Maybe you have an add function, a sub function, a mul functions and a div function. These are all defined in
a file arithmetic.c, but the table (maybe in another file) needs to see declarations of the names:
typedef double operation(double, double);
/* ... */
extern operation add, sub, mul, div;
static struct {
char *name;
operation *function;
} ops[] = {
{ "add", add },
{ "subtract", sub },
{ "multiply", mul },
{ "divide", div }
};
On 02/01/2026 23:18, Chris M. Thomasson wrote:
On 1/2/2026 1:52 PM, Kaz Kylheku wrote:
On 2026-01-02, Michael Sanders <porkchop@invalid.foo> wrote:
On Fri, 2 Jan 2026 17:48:16 -0000 (UTC), Kaz Kylheku wrote:
On 2026-01-02, Michael Sanders <porkchop@invalid.foo> wrote:
B: because every function must have a return type
-a-a-a *including function pointers*?
What it is you think type is, in the context of C?
Does type survive into run-time?
If a function pointer is missing type information about return
type, and
that function pointer is needed for expressing a function call, where >>>>> does the compiler get the type from?
Its void that's throwing me Kaz. I'm not sure what to think when
it comes to void pointers.
Because you teleported here from 1985.
[...]
One note, void* cannot hold a function pointer without getting
undefined or implementation-defined behavior.
Kaz mentioned several types that "void *" is a generic /object/ pointer.
-aFunctions are not objects - pointers to functions are completely different from pointers to objects.-a You can't mix them without "I know what I am doing" explicit casts, with non-portable behaviour and a
serious risk of UB.
typedef void (*generic_func_ptr)(void)
There is no generic function pointer type equivalent to "void *" - you
are always going to need casts when converting between different pointer types.-a And you /really/ need to make sure you convert to exactly the correct type before calling the pointed-to function.-a But the definition you gave here is commonly used when people want to have generic function pointers.
struct object_prv_vtable {And interesting piece of trivia about C function types and function type compatibility rules is that:
int (*fp_destroy) (void* const);
};
... with a remark that `extern` is completely redundant here.
struct object_prv_vtable {
int (*fp_destroy) (void* const);
};
...
(mp_self)->vtable->object.fp_destroy((mp_self)) \
On Fri, 2 Jan 2026 17:48:16 -0000 (UTC), Kaz Kylheku wrote:[restore snippage]
On 2026-01-02, Michael Sanders <porkchop@invalid.foo> wrote:
...void moo(char HISTORY[][64], int hst_len, int invalid, const char
*gme_msg)
void mastermind(char HISTORY[][64], int hst_len, int invalid, const
char *gme_msg)
to use either i have:
void (*render)(char [][64], int, int, const char *) = MOO ? moo :
mastermind;
my multi-part question:
why is void required for the function pointer?
Its void that's throwing me Kaz. I'm not sure what to think when
it comes to void pointers.
A void pointer (the pointer itself) is not of the type
it points to no? ...
... Its typeless & unknown at compile time
A 'normal' pointer in C is a variable that stores the address
of another variable. And *is of the type it points to*...
char str[] = "learning publicly is a humbling experience.";123456789012345678901234567890
char *ptr = str;
Then void enters stage left...
void *genericPointer;
It can point to an integer, a character, a structure, or any other type.
void *genericPointer = &intValue; void *genericPointer = str[];void* is a pointer type that can point at objects of any type. However,
And it seems to survive a lack of type at compile time.
Man I'm confused on this. I mean I get void is flexible,
but 'void' is not void in any sense... its like a mask in some
way (I'm groping for a definition) I need to study void more.
On 02/01/2026 23:18, Chris M. Thomasson wrote:[...]
typedef void (*generic_func_ptr)(void)
There is no generic function pointer type equivalent to "void *" - you
are always going to need casts when converting between different
pointer types. And you /really/ need to make sure you convert to
exactly the correct type before calling the pointed-to function. But
the definition you gave here is commonly used when people want to have generic function pointers.
Similarly, in C, the declaration "int foo()" is an old-style K&R[...]
declaration that says that foo is a function that takes an unspecified
number of arguments of unspecified types. K&R declarations were found to
be too error-prone, but the language still allows them for backwards compatibility. When C was first standardized, K&R declarations were
replace with prototype declarations which specified the type of each argument, which allowed error checking.
The natural way for a prototype declaration to say "no arguments" would
have been to simply specify no arguments, and C++ uses that approach. However, since C still supports the old K&R declarations, a different
method was used. "int foo(void)" is a function prototype that says that
foo doesn't take any arguments.
On Sat, 3 Jan 2026 07:41:33 -0800, Andrey Tarasevich wrote:
... with a remark that `extern` is completely redundant here.
One of those quirks of C, that rCLexternrCY means something in some
places, but is redundant in others.
David Brown <david.brown@hesbynett.no> writes:
On 02/01/2026 23:18, Chris M. Thomasson wrote:[...]
typedef void (*generic_func_ptr)(void)
There is no generic function pointer type equivalent to "void *" - you
are always going to need casts when converting between different
pointer types. And you /really/ need to make sure you convert to
exactly the correct type before calling the pointed-to function. But
the definition you gave here is commonly used when people want to have
generic function pointers.
Yes, but ...
There is no generic function pointer type equivalent to void* in the
sense that values can be converted to and from the type implicitly.
For example:
int n = 42;
void *vp = &n; // implicit conversion
double *dp = vp; // implicit conversion, potentially dangerous
There are no such implicit conversions for function pointer types.
However, in the sense of a round-trip conversion yielding the
original result *all* function pointer types can be treated as
generic function pointer types. You just have to use casts to do
any conversions.
It's very common for all pointer types in a given implementation
to have the same representation, and for round-trip conversions
to safely yield the original value, but it's not guaranteed by
the language, and there are implementations where, for example,
function pointer types are bigger than object pointer types, or
void* is bigger than int*. Certain collections of pointer types
are guaranteed by the language to have the same representation:
- void*, char*, signed char*, unsigned char*
- All struct pointer types
- All union pointer types
- All function pointer types
Incidentally, if I want a generic function pointer type, I might
define it so that it can't be used accidentally without a cast.
For example:
typedef struct dummy__ (generic_function)(void);
where struct dummy__ is an incomplete type. (Note that I've
typedef'ed the function type, not the pointer type.) But that
might be considered overkill; treating a void function with
no parameters as generic is probably good enough.
[...]
char str[] = "learning publicly is a humbling experience.";12345678901234567890\
char *ptr = str;In this case, str has the type "char[
On Fri, 2 Jan 2026 21:52:43 -0000 (UTC), Kaz Kylheku wrote:
Because you teleported here from 1985.
Get out of here Kaz.
But seriously, thanks for the detailed
reply. I move forward day-by-day.
On 1/2/2026 10:08 PM, Michael Sanders wrote:
On Fri, 2 Jan 2026 21:52:43 -0000 (UTC), Kaz Kylheku wrote:
Because you teleported here from 1985.
Get out of here Kaz.
Why? Kaz is a smart guy!
I might have questions down the road...
rCLEvaluate *foo, but explicitly discard its value.rCY
It is a cast-to-void used to silence warnings about an
unused expression or unused result.
My question: Why?
On Mon, 5 Jan 2026 08:39:53 -0000 (UTC), Michael Sanders wrote:
I might have questions down the road...
One more question, but 1st the context...
I asked ChatGPT this question:
In C, what is the most common meaning of (void) *foo
Its reply:
In C, (void) *foo most commonly means:
rCLEvaluate *foo, but explicitly discard its value.rCY
It is a cast-to-void used to silence warnings about an
unused expression or unused result.
My question: Why?
On Tue, 6 Jan 2026 12:32:43 -0000 (UTC)
Michael Sanders <porkchop@invalid.foo> wrote:
On Mon, 5 Jan 2026 08:39:53 -0000 (UTC), Michael Sanders wrote:
I might have questions down the road...
One more question, but 1st the context...
I asked ChatGPT this question:
In C, what is the most common meaning of (void) *foo
Its reply:
In C, (void) *foo most commonly means:
rCLEvaluate *foo, but explicitly discard its value.rCY
It is a cast-to-void used to silence warnings about an
unused expression or unused result.
My question: Why?
Why what?
There exist a need to selectively silence otherwise useful compiler
warnings. There are no standard ways to do it.
So compilers gave to user ways to express their wishes.
I would speculate that in case of this particular pattern it happened initially by accident - users found a way to exploit weakness in
compiler's warning logic to achieve desired effect.
Since the usage became widespread, compiler vendors paid attention and
turned accidental behavior into semi-official.
There is similar convention w.r.t. unused function parameters that is
even more widespread.
Does it answer your question or you already knew that and asked about something else?
On 1/6/26 1:32 PM, Michael Sanders wrote:
rCLEvaluate *foo, but explicitly discard its value.rCY
It is a cast-to-void used to silence warnings about an
unused expression or unused result.
My question: Why?
I'll throw a guess on the wall :)
In C++ you might want to get side effect from some overloaded
operator. In C the only reason I could think of for for
evaluating *foo and discarding the result would be if *foo
was volatile, and in this way you are poking at some
hardware-mapped address.
On 1/6/26 1:32 PM, Michael Sanders wrote:
rCLEvaluate *foo, but explicitly discard its value.rCY
It is a cast-to-void used to silence warnings about an
unused expression or unused result.
My question: Why?
I'll throw a guess on the wall :)
In C++ you might want to get side effect from some overloaded
operator. In C the only reason I could think of for for
evaluating *foo and discarding the result would be if *foo
was volatile, and in this way you are poking at some
hardware-mapped address.
On Mon, 5 Jan 2026 08:39:53 -0000 (UTC), Michael Sanders wrote:
I might have questions down the road...
One more question, but 1st the context...
I asked ChatGPT this question:
In C, what is the most common meaning of (void) *foo
Its reply:
In C, (void) *foo most commonly means:
rCLEvaluate *foo, but explicitly discard its value.rCY
It is a cast-to-void used to silence warnings about an
unused expression or unused result.
My question: Why?
On Mon, 5 Jan 2026 08:39:53 -0000 (UTC), Michael Sanders wrote:
I might have questions down the road...
One more question, but 1st the context...
I asked ChatGPT this question:
In C, what is the most common meaning of (void) *foo
Its reply:
In C, (void) *foo most commonly means:
rCLEvaluate *foo, but explicitly discard its value.rCY
It is a cast-to-void used to silence warnings about an
unused expression or unused result.
My question: Why?
On Mon, 5 Jan 2026 08:39:53 -0000 (UTC), Michael Sanders wrote:
I might have questions down the road...
One more question, but 1st the context...
I asked ChatGPT this question:
In C, what is the most common meaning of (void) *foo
But sometimes you know you don't need the variables or parameters, but
you might still want to have the declarations there. Maybe they are
used with some builds with different conditional compilation, or you
know you might need them later. Maybe you have extra parameters because
the function has to fit a particular set of parameter types, even though
in some cases you don't need them all. (In C23, you can leave a
parameter unnamed in the definition - but not prior to C23.)
Perhaps the access has side effects[*]. Software might only care
about the side effect of the access, not the result returned.
[*] For example, an access to a memory mapped control register.
I'm curious - in what context did you encounter that code? As written,
it's an expression, and foo would have to be a pointer to an object,
which would be a change of subject from the previous messages in this
thread.
However,
(void) *foo;
would be a declaration equivalent to
void *foo;
which is a pointer to void, which would fit the context of our previous discussion. Could that be what you're actually asking about?
Shoot, its just that I lack at the moment the C-specific vocabulary to describe what I'm wondering about James. Frustrating trust me =) ...
... Mainly
its that void seems to be used across multiple contexts (in differing ways).
On 02/01/2026 23:18, Chris M. Thomasson wrote:
On 1/2/2026 1:52 PM, Kaz Kylheku wrote:
On 2026-01-02, Michael Sanders <porkchop@invalid.foo> wrote:
On Fri, 2 Jan 2026 17:48:16 -0000 (UTC), Kaz Kylheku wrote:
On 2026-01-02, Michael Sanders <porkchop@invalid.foo> wrote:
B: because every function must have a return type
-a-a-a *including function pointers*?
What it is you think type is, in the context of C?
Does type survive into run-time?
If a function pointer is missing type information about return type, >>>>> and
that function pointer is needed for expressing a function call, where >>>>> does the compiler get the type from?
Its void that's throwing me Kaz. I'm not sure what to think when
it comes to void pointers.
Because you teleported here from 1985.
[...]
One note, void* cannot hold a function pointer without getting undefined
or implementation-defined behavior.
Kaz mentioned several types that "void *" is a generic /object/ pointer.
Functions are not objects - pointers to functions are completely
different from pointers to objects. You can't mix them without "I know
what I am doing" explicit casts, with non-portable behaviour and a
serious risk of UB.
On Fri 1/2/2026 7:33 PM, Ben Bacarisse wrote:
A pattern I've used more than once when setting up a table of function
pointers that act like op-codes. Maybe you have an add function, a sub
function, a mul functions and a div function. These are all defined in
a file arithmetic.c, but the table (maybe in another file) needs to see
declarations of the names:
typedef double operation(double, double);
/* ... */
extern operation add, sub, mul, div;
static struct {
char *name;
operation *function;
} ops[] = {
{ "add", add },
{ "subtract", sub },
{ "multiply", mul },
{ "divide", div }
};
... with a remark that `extern` is completely redundant here.
operation add, sub, mul, div;
Well, '(void)*foo' effectively says "foo" is not a null pointer
(otherwise if would be undefined behaviour). Good optimizer
will_not_ dereference foo, but keep information for further
use.
On 1/6/26 3:50 PM, Waldek Hebisch wrote:
Well, '(void)*foo' effectively says "foo" is not a null pointer
(otherwise if would be undefined behaviour). Good optimizer
will_not_ dereference foo, but keep information for further
use.
Hehe, now that I understand more of UB, that is correct, but
I don't think you can *rely* on the optimizer behavior, can you?
On 2026-01-03, David Brown <david.brown@hesbynett.no> wrote:[...]
Kaz mentioned several types that "void *" is a generic /object/ pointer.
Functions are not objects - pointers to functions are completely
different from pointers to objects. You can't mix them without "I know
what I am doing" explicit casts, with non-portable behaviour and a
serious risk of UB.
"I know that I'm on POSIX" goes a long way also.
On Mon, 5 Jan 2026 12:40:08 -0800, Chris M. Thomasson wrote:
On 1/2/2026 10:08 PM, Michael Sanders wrote:
On Fri, 2 Jan 2026 21:52:43 -0000 (UTC), Kaz Kylheku wrote:
Because you teleported here from 1985.
Get out of here Kaz.
Why? Kaz is a smart guy!
I'm teasing Kaz. I know him from other haunts.
A sharp guy - its all good.
On 1/6/26 3:50 PM, Waldek Hebisch wrote:
Well, '(void)*foo' effectively says "foo" is not a null pointer
(otherwise if would be undefined behaviour). Good optimizer
will_not_ dereference foo, but keep information for further
use.
Hehe, now that I understand more of UB, that is correct, but
I don't think you can *rely* on the optimizer behavior, can you?
highcrew <high.crew3868@fastmail.com> wrote:
On 1/6/26 3:50 PM, Waldek Hebisch wrote:
Well, '(void)*foo' effectively says "foo" is not a null pointer
(otherwise if would be undefined behaviour). Good optimizer
will_not_ dereference foo, but keep information for further
use.
Hehe, now that I understand more of UB, that is correct, but
I don't think you can *rely* on the optimizer behavior, can you?
Well, there is no warranty, but IME gcc is pretty reliable at
removing such accesses.
highcrew <high.crew3868@fastmail.com> writes:
On 1/6/26 3:50 PM, Waldek Hebisch wrote:
Well, '(void)*foo' effectively says "foo" is not a null pointer
(otherwise if would be undefined behaviour). Good optimizer
will_not_ dereference foo, but keep information for further
use.
Hehe, now that I understand more of UB, that is correct, but
I don't think you can *rely* on the optimizer behavior, can you?
Say you have a function xxx defined in a header file, but that
function is only used in certain source files that include that
header file.
int
xxx(const char *a, size_t b)
{
/* do something with a and b */
}
When compiling with -Wall, a translation unit that doesn't
reference 'xxx' yet requires other components of the
header file will get a warning (error with -Werror) that
the function was unreferenced. It is not uncommon to
make a void reference to the function in those translation
units to avoid the warning, e.g.
yyy.c:
#include "xxx.h"
int
yyy(...)
{
<function body>
(void)xxx;
}
xxx in this context devolves to a function pointer, IIUC.
typedef double operation(double, double);
/* ... */
extern operation add, sub, mul, div;
static struct {
char *name;
operation *function;
} ops[] = {
{ "add", add },
{ "subtract", sub },
{ "multiply", mul },
{ "divide", div }
};
... with a remark that `extern` is completely redundant here.
operation add, sub, mul, div;
Butlonly because operation is a function type, so the concept of
a tentative definition doesn't apply.
The lack of extern could trip someone up who refactors the
code such that the operations are object types:
named_operation add, sub, mul, div; // oops, multiple definition
static named_operation ops[] = { add, sub, mul, div };
On Sat 1/3/2026 12:04 PM, Chris M. Thomasson wrote:
struct object_prv_vtable {
int (*fp_destroy) (void* const);
};
And interesting piece of trivia about C function types and function
type compatibility rules is that:
1. Top-level qualifiers on function parameters are preserved as part
of function type.
However, such top-level qualifiers are ignored when
determining function type compatibility.
Andrey Tarasevich <noone@noone.net> writes:
On Sat 1/3/2026 12:04 PM, Chris M. Thomasson wrote:
struct object_prv_vtable {
int (*fp_destroy) (void* const);
};
And interesting piece of trivia about C function types and function
type compatibility rules is that:
1. Top-level qualifiers on function parameters are preserved as part
of function type.
Not completely wrong but not exactly right either.
However, such top-level qualifiers are ignored when
determining function type compatibility.
It's easier to take the point of view that top-level qualifiers
for function parameters don't participate in the type of the
function as a whole. Taking that view is easier to understand
and gives results that are indistinguishable from the actual
rules.
which is related to how qualifications are treated under `_Generic`. `_Generic` operates on "exact match" basis not on "type compatibility" basis. Which is why such matters suddenly become important.
Believe me, I understand. A large part of the messages I post in this
forum are devoted to explaining to people the correct terminology, and
trying to convince them to use it, in order to avoid confusion. Some
people are very resistant to avoiding confusion - they seem to like it.
Summarizing what I wrote before, there's exactly three unrelated ways
void is used:
void foo(int); // Declares that foo does not return any value.
int bar(void); // Declares that bar does not take any arguments
void *foobar;
// Declares that foobar points at an object of unspecified type.
On Wed 1/7/2026 7:35 AM, Tim Rentsch wrote:
Andrey Tarasevich <noone@noone.net> writes:
On Sat 1/3/2026 12:04 PM, Chris M. Thomasson wrote:
struct object_prv_vtable {
int (*fp_destroy) (void* const);
};
And interesting piece of trivia about C function types and function
type compatibility rules is that:
1. Top-level qualifiers on function parameters are preserved as part
of function type.
Not completely wrong but not exactly right either.
However, such top-level qualifiers are ignored when
determining function type compatibility.
It's easier to take the point of view that top-level qualifiers
for function parameters don't participate in the type of the
function as a whole. Taking that view is easier to understand
and gives results that are indistinguishable from the actual
rules.
No, that's not entirely accurate.
The C17 modifications I mentioned in my previous post stems from DR#423
https://www.open-std.org/jtc1/sc22/wg14/www/docs/summary.htm#dr_423
which is related to how qualifications are treated under
_Generic`. `_Generic` operates on "exact match" basis not on "type compatibility" basis. Which is why such matters suddenly become
important.
The DR itself is about qualifications on rvalues (another thing that
"did not matter" previously), not about function parameters. But it is
clear that it applies to our topic as well.
I have no time to research it further at the moment (will do it a bit
later), but something tells me that `_Generic` is expected to "see"
and distinguish the exact const-qualification of function parameters
in function types. If so, it might be a "useless" feature, but still..
On Wed 1/7/2026 8:17 AM, Andrey Tarasevich wrote:
which is related to how qualifications are treated under
_Generic`. `_Generic` operates on "exact match" basis not on "type
compatibility" basis. Which is why such matters suddenly become
important.
No, I take it back. `_Generic` chooses its branches based on type compatibility.
In that case it raises an interesting question: why does the C
standard keeps sticking to this, i.e. keeps persistent top-level
qualifiers on function parameters? Why not switch to C++-like approach
and just discard such qualifiers at the parameter type adjustment
stage? Especially now, after C17 started to explicitly do this with
the return type.
On Tue 1/6/2026 12:41 PM, Kaz Kylheku wrote:
typedef double operation(double, double);
/* ... */
extern operation add, sub, mul, div;
static struct {
char *name;
operation *function;
} ops[] = {
{ "add", add },
{ "subtract", sub },
{ "multiply", mul },
{ "divide", div }
};
... with a remark that `extern` is completely redundant here.
operation add, sub, mul, div;
Butlonly because operation is a function type, so the concept of
a tentative definition doesn't apply.
Yes, to me it feels like it has way less potential to mislead with an extern`. But I can't really say whether this perception is objectively inherent in the construct, or it is just the fact that it is an exotic
way of declaring functions (for me and, probably, for most people).
However, while it is true that the concept of a tentative definition
doesn't apply, I still don't quite get your point. What if were an
object type and the concept would apply? Are you implying that
tentative definitions should be avoided (i.e. that all object
definitions should include an initializer)?
The lack of extern could trip someone up who refactors the
code such that the operations are object types:
named_operation add, sub, mul, div; // oops, multiple definition
static named_operation ops[] = { add, sub, mul, div };
Again, I'm at a bit of a loss. What is this intended to illustrate?
Andrey Tarasevich <noone@noone.net> writes:
On Tue 1/6/2026 12:41 PM, Kaz Kylheku wrote:
typedef double operation(double, double);
/* ... */
extern operation add, sub, mul, div;
static struct {
char *name;
operation *function;
} ops[] = {
{ "add", add },
{ "subtract", sub },
{ "multiply", mul },
{ "divide", div }
};
... with a remark that `extern` is completely redundant here.
operation add, sub, mul, div;
Butlonly because operation is a function type, so the concept of
a tentative definition doesn't apply.
Yes, to me it feels like it has way less potential to mislead with an
extern`. But I can't really say whether this perception is objectively
inherent in the construct, or it is just the fact that it is an exotic
way of declaring functions (for me and, probably, for most people).
However, while it is true that the concept of a tentative definition
doesn't apply, I still don't quite get your point. What if were an
object type and the concept would apply? Are you implying that
tentative definitions should be avoided (i.e. that all object
definitions should include an initializer)?
If we ignore "static" for the moment, there is a simple rule:
use 'extern' for declarations, and nothing for definitions.
This rule works for both functions and objects.
Now if we add "static" back into the mix, and limit the discussion
to functions, the same rule applies provided we stipulate that
everything is pre-declared. Thus, always use 'extern' or 'static'
to declare (in advance) a function, and on the function definition
don't use any storage class.
Unfortunately, the way 'static' works for objects is not symmetric.
There is no way to write a non-defining declaration for a static
object. And, what is worse, once an object has been declared (and
so tentatively defined) with 'static', then any definition must also
use 'static'. Hence we have a new rule: always use 'extern' or
'static' when declaring a function or object, and leave off both
when defining a function or object, /except/ 'static' must be used
when defining a static object. C would have been nicer if 'static'
for objects worked the same way as 'static' for functions. Oh well.
On 2026-01-06 07:32, Michael Sanders wrote:
On Mon, 5 Jan 2026 08:39:53 -0000 (UTC), Michael Sanders wrote:
I might have questions down the road...
In the message you were responding to, I was talking about declarations,
not expressions.
One more question, but 1st the context...
I asked ChatGPT this question:
In C, what is the most common meaning of (void) *foo
I'm curious - in what context did you encounter that code? As written,
it's an expression, and foo would have to be a pointer to an object,
which would be a change of subject from the previous messages in this
thread.
However,
(void) *foo;
would be a declaration equivalent to
void *foo;
which is a pointer to void, which would fit the context of our previous discussion. Could that be what you're actually asking about?
However,
(void) *foo;
would be a declaration equivalent to
void *foo;
which is a pointer to void, which would fit the context of our previous discussion. Could that be what you're actually asking about?
On Tue 1/6/2026 7:58 AM, James Kuyper wrote:
However,
-a-a-a-a-a (void) *foo;
would be a declaration equivalent to
-a-a-a-a void *foo;
which is a pointer to void, which would fit the context of our previous
discussion. Could that be what you're actually asking about?
Um... I believe Tim Rentsch is correct in stating that C declaration
syntax does not allow this. When it comes to 'declaration-specifiers' portion of the declaration, the grammar is pretty strict in not allowing
and redundant parentheses to slip through. You can't simply parenthesize
the type name and still expect it to match the 'declaration-specifiers' grammar.
The 'init-declarator-list' side is way more permissive in that regard
-a int (a); /* equivalent to `int a;` */
but not what you stated above.
P.S. On a loosely related note: the C++-like grammatical ambiguity
between a function call and a declaration, present in
-a { foo(x); }
is technically present in C as well, but it is prevented by the fact
that there's simply no way to declare `foo` as a function and as a
typedef name without having one name hide another.
On Tue 1/6/2026 7:58 AM, James Kuyper wrote:
However,
(void) *foo;
would be a declaration equivalent to
void *foo;
which is a pointer to void, which would fit the context of our previous
discussion. Could that be what you're actually asking about?
Um... I believe Tim Rentsch is correct in stating that C declaration
syntax does not allow this. [...]
| Sysop: | Amessyroom |
|---|---|
| Location: | Fayetteville, NC |
| Users: | 54 |
| Nodes: | 6 (0 / 6) |
| Uptime: | 01:34:32 |
| Calls: | 743 |
| Files: | 1,218 |
| Messages: | 187,735 |