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:
“Evaluate *foo, but explicitly discard its value.”
It is a cast-to-void used to silence warnings about an
unused expression or unused result.
My question: Why?
On 1/6/26 1:32 PM, Michael Sanders wrote:
“Evaluate *foo, but explicitly discard its value.”
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 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:
“Evaluate *foo, but explicitly discard its value.”
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:
“Evaluate *foo, but explicitly discard its value.”
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:
“Evaluate *foo, but explicitly discard its value.”
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:
“Evaluate *foo, but explicitly discard its value.”
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
*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,
(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. 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
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
{ 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: | DaiTengu |
|---|---|
| Location: | Appleton, WI |
| Users: | 1,097 |
| Nodes: | 10 (0 / 10) |
| Uptime: | 21:15:42 |
| Calls: | 14,089 |
| Files: | 187,111 |
| D/L today: |
1,315 files (438M bytes) |
| Messages: | 2,490,431 |