• Re: function pointer question

    From Michael S@already5chosen@yahoo.com to comp.lang.c on Tue Jan 6 15:47:41 2026
    From Newsgroup: comp.lang.c

    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?
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael Sanders@porkchop@invalid.foo to comp.lang.c on Tue Jan 6 13:57:03 2026
    From Newsgroup: comp.lang.c

    On Tue, 6 Jan 2026 13:59:20 +0100, highcrew wrote:

    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.

    I can see that but... why not just declare as volatile in that case?
    --
    :wq
    Mike Sanders
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael Sanders@porkchop@invalid.foo to comp.lang.c on Tue Jan 6 14:01:37 2026
    From Newsgroup: comp.lang.c

    On Tue, 6 Jan 2026 15:47:41 +0200, Michael S wrote:

    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?

    If its unused, by definition its /unused/...

    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.

    I cant help but think this is the reason too.

    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?

    Well, lots of you guys here either do or/did this for a living.
    Me? I'm a weekend warrior as it were, so lots of questions about
    some of those dark corners...
    --
    :wq
    Mike Sanders
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From antispam@antispam@fricas.org (Waldek Hebisch) to comp.lang.c on Tue Jan 6 14:50:47 2026
    From Newsgroup: comp.lang.c

    highcrew <high.crew3868@fastmail.com> wrote:
    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.

    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.
    --
    Waldek Hebisch
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Tue Jan 6 15:55:37 2026
    From Newsgroup: comp.lang.c

    On 06/01/2026 13:32, Michael Sanders 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?


    It is not uncommon for people to have "-Wunused" warnings enabled in
    their builds. If you have declared variables in a function, and
    possibly assigned values to them, but don't read them or use them,
    that's likely a mistake in your code. The compiler can eliminate the
    unused variables and associated calculations, but a warning can remind
    you that your function is perhaps not finished yet. Similarly, warnings
    on unused parameters can be helpful if you have forgotten something.

    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.)

    As a way to silence such warnings - or as an indication to human readers
    that you know you don't need the value - you can cast the value to void.

    It can also be used for discarding values from a function return marked "[[nodiscard]]" in C23 (or using equivalent compiler-specific features
    prior to C23), or after reading a volatile variable when you want the
    read to be done, but don't care about the value.

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From scott@scott@slp53.sl.home (Scott Lurndal) to comp.lang.c on Tue Jan 6 15:41:59 2026
    From Newsgroup: comp.lang.c

    Michael Sanders <porkchop@invalid.foo> writes:
    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?

    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.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From James Kuyper@jameskuyper@alumni.caltech.edu to comp.lang.c on Tue Jan 6 10:58:36 2026
    From Newsgroup: comp.lang.c

    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?

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael Sanders@porkchop@invalid.foo to comp.lang.c on Tue Jan 6 16:44:13 2026
    From Newsgroup: comp.lang.c

    On Tue, 6 Jan 2026 15:55:37 +0100, David Brown wrote:

    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.)

    Hmm. Yes this makes sense too. Thanks David I'm soaking up the knowledge
    as quickly as I can =)
    --
    :wq
    Mike Sanders
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael Sanders@porkchop@invalid.foo to comp.lang.c on Tue Jan 6 16:45:25 2026
    From Newsgroup: comp.lang.c

    On Tue, 06 Jan 2026 15:41:59 GMT, Scott Lurndal wrote:

    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.

    That's a good candidate too!
    --
    :wq
    Mike Sanders
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael Sanders@porkchop@invalid.foo to comp.lang.c on Tue Jan 6 16:49:45 2026
    From Newsgroup: comp.lang.c

    On Tue, 6 Jan 2026 10:58:36 -0500, James Kuyper wrote:

    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.

    I just just managed to get myself confused because there seems to be
    more than one way to use void.

    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). Keith was saying void has special properties...

    Nevertheless, I bumbled through it. Here are the functions:

    void mastermind(int hst_len, const char *gme_msg);
    void moo(int hst_len, const char *gme_msg);
    void bagels(int hst_len, const char *gme_msg);

    Here's the latest way I'm using them...

    static void (*mode[])(int, const char *) = { mastermind, moo, bagels };
    void (*render)(int, const char *) = mode[app.idx];

    Welp, back to studying for me.
    --
    :wq
    Mike Sanders
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From James Kuyper@jameskuyper@alumni.caltech.edu to comp.lang.c on Tue Jan 6 12:09:58 2026
    From Newsgroup: comp.lang.c

    On 2026-01-06 11:49, Michael Sanders wrote:
    ...
    Shoot, its just that I lack at the moment the C-specific vocabulary to describe what I'm wondering about James. Frustrating trust me =) ...

    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.

    ... Mainly
    its that void seems to be used across multiple contexts (in differing ways).

    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.

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Kaz Kylheku@046-301-5902@kylheku.com to comp.lang.c on Tue Jan 6 20:33:48 2026
    From Newsgroup: comp.lang.c

    On 2026-01-03, David Brown <david.brown@hesbynett.no> wrote:
    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.

    "I know that I'm on POSIX" goes a long way also.
    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @Kazinator@mstdn.ca
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Kaz Kylheku@046-301-5902@kylheku.com to comp.lang.c on Tue Jan 6 20:41:34 2026
    From Newsgroup: comp.lang.c

    On 2026-01-03, Andrey Tarasevich <noone@noone.net> wrote:
    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;

    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 };
    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @Kazinator@mstdn.ca
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From highcrew@high.crew3868@fastmail.com to comp.lang.c on Tue Jan 6 21:44:00 2026
    From Newsgroup: comp.lang.c

    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?
    --
    High Crew
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From scott@scott@slp53.sl.home (Scott Lurndal) to comp.lang.c on Tue Jan 6 22:08:39 2026
    From Newsgroup: comp.lang.c

    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.

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Tue Jan 6 17:01:40 2026
    From Newsgroup: comp.lang.c

    Kaz Kylheku <046-301-5902@kylheku.com> writes:
    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.

    Sort of.

    POSIX doesn't guarantee that all function pointers can be converted
    to void* without loss of information. It makes that guarantee only
    for pointers returned by dlsym().

    https://pubs.opengroup.org/onlinepubs/9799919799/functions/dlsym.html

    On the other hand, I'd be surprised if there were any
    POSIX-conforming implementation that don't make that guarantee for
    all function pointers. There are some tricks that could be played
    to satisfy the dlsym() requirement even if function pointers are
    bigger than void*, but I've never heard of an implementation that
    did anything like that.

    (For example, say the system has 64-bit function pointers and
    32-bit void*. The implementation could arrange for every function
    referenced by dlsym() to have an address with its upper 32 bits set
    to zero, perhaps by using small wrapper functions. Other functions
    might have arbitrary addresses so converting them to void* would
    lose information.)
    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.lang.c on Tue Jan 6 17:05:45 2026
    From Newsgroup: comp.lang.c

    On 1/5/2026 8:30 PM, Michael Sanders wrote:
    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.



    :^)
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From antispam@antispam@fricas.org (Waldek Hebisch) to comp.lang.c on Wed Jan 7 09:25:31 2026
    From Newsgroup: comp.lang.c

    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.
    --
    Waldek Hebisch
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Wed Jan 7 11:37:11 2026
    From Newsgroup: comp.lang.c

    On 07/01/2026 10:25, Waldek Hebisch wrote:
    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.


    gcc is pretty good at not evaluating an expression that is cast to void (unless it has side-effects). But it is not good at using the
    assumption that "foo" is not null in later code.


    If you just want to tell the compiler (and human readers) that "foo" is
    not null, you can be more explicit :


    C23 :
    #include <stddef.h>
    if (!foo) unreachable();

    Newer gcc :

    __attribute__((assume(foo)));

    Older gcc :

    if (!foo) __builtin_unreachable();


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Wed Jan 7 05:59:39 2026
    From Newsgroup: comp.lang.c

    scott@slp53.sl.home (Scott Lurndal) writes:

    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;
    }

    It seems better to address this problem in the header file
    itself. It's easy to do that by adding a second function
    in the header file and having them mutually reference
    each other.

    xxx in this context devolves to a function pointer, IIUC.

    Just use (void)&xxx. Now you're sure.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andrey Tarasevich@noone@noone.net to comp.lang.c on Wed Jan 7 07:18:31 2026
    From Newsgroup: comp.lang.c

    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?
    --
    Best regards,
    Andrey
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Wed Jan 7 07:35:17 2026
    From Newsgroup: comp.lang.c

    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.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andrey Tarasevich@noone@noone.net to comp.lang.c on Wed Jan 7 08:17:24 2026
    From Newsgroup: comp.lang.c

    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..
    --
    Best regards,
    Andrey

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andrey Tarasevich@noone@noone.net to comp.lang.c on Wed Jan 7 08:23:29 2026
    From Newsgroup: comp.lang.c

    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.
    --
    Best regards,
    Andrey
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael Sanders@porkchop@invalid.foo to comp.lang.c on Wed Jan 7 21:18:28 2026
    From Newsgroup: comp.lang.c

    On Tue, 6 Jan 2026 12:09:58 -0500, James Kuyper wrote:

    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.

    Oh yeah & I very much appreciate you, Kieth, everyone else too.
    Its all good, I'll just roll with it & get there a step at a time.

    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.

    Got it, nice succinct answer.
    --
    :wq
    Mike Sanders
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Wed Jan 7 18:27:02 2026
    From Newsgroup: comp.lang.c

    Andrey Tarasevich <noone@noone.net> writes:

    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..

    I see you have posted a further followup. I am responding to
    that.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Wed Jan 7 18:44:05 2026
    From Newsgroup: comp.lang.c

    Andrey Tarasevich <noone@noone.net> writes:

    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.

    Right. For _Generic, top-level qualifiers are dropped (IIUC).

    Incidental comment: the discussion in DR 423 leaves much to be
    desired.

    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.

    My guess is that's a consequence of the processes used to write the
    ISO C standard and to modify the ISO C standard. A lot of work goes
    into both writing the text initially and revising the text later when
    a change is needed (talking about a change to the text, which could be
    either a modification of an earlier semantics or a clarification of an
    earlier semantics). Sometimes there is a sense that a smaller change
    would mean less work and also a smaller chance of unintended problems
    (and errors), so a smaller change is chosen even though the end result
    is less attractive. Perhaps that happened here, in much the same way
    that modifying source code might choose an easier path locally to the
    detriment of some larger overall aesthetic.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Wed Jan 7 21:52:16 2026
    From Newsgroup: comp.lang.c

    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.


    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?

    To me the example looks flawed (and not because of multiple
    definitions). My advice is to stick with the rule described above
    for declarations and definitions.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Thu Jan 8 09:17:34 2026
    From Newsgroup: comp.lang.c

    On 08/01/2026 06:52, Tim Rentsch wrote:
    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.


    I use a different rule that is more consistent - at least for the way I organise my code. For different organisations of code and files, it
    might not work as well.

    Any function or object that is to be externally linked is declared with "extern" in an appropriate header (usually "file.h", where the
    definition is in "file.c", but there are occasional exceptions). Inside
    the C file, functions and objects are defined either with "static" (for internal linkage) or without a storage class specifier (for external
    linkage). The C file always includes the header that declares its
    extern functions and objects, so that the compiler can check for
    consistency.

    If I need to have a forward declaration of a static function (I rarely
    do, but that can vary by coding style), the forward declaration is made "static" and the function definition is also marked "static". While you
    could omit the "static" on the definition after a forward declaration, I
    think it is good to keep it for consistency and clarity (similar to the
    use of "extern" on function declarations in headers).


    The result is greater consistency than your scheme, and without the need
    for a redundant table of forward declarations for static functions. (I
    have never really understood why some people like such lists - they give
    no new information, force you to jump around back and forth in the file
    when adding or changing functions, can easily get inconsistent, and tell
    you nothing that you can't easily see with any half-decent IDE or
    programmer's editor).


    And the scheme can easily be checked with gcc warning flags:

    -Wredundant-decls
    -Wmissing-declarations


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Fri Jan 9 09:14:22 2026
    From Newsgroup: comp.lang.c

    James Kuyper <jameskuyper@alumni.caltech.edu> writes:

    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,

    That statement is simply wrong. The identifier foo could name a
    function, or be of type pointer to function, or be of type pointer
    to an object type (and whose value might or might not point to an
    object). A compiler might issue a diagnostic if foo has a type
    that is a pointer to an incomplete object type, but ABICD the C
    standard doesn't actually require that; the constraint says that
    the operand "shall have pointer type".

    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?

    In what context is '(void) *foo;' considered a declaration?
    AFAICT it doesn't satisfy the syntax rules of any version
    of ISO C.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andrey Tarasevich@noone@noone.net to comp.lang.c on Sat Jan 10 19:17:35 2026
    From Newsgroup: comp.lang.c

    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.
    --
    Best regards,
    Andrey
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From James Russell Kuyper Jr.@jameskuyper@alumni.caltech.edu to comp.lang.c on Sat Jan 10 22:39:37 2026
    From Newsgroup: comp.lang.c

    On 2026-01-10 22:17, Andrey Tarasevich wrote:
    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.


    I had remembered that parantheses could be optionally (and pointlessly)
    added surrounding a declarator (6.7.7.1p6). Since it's a feature I would
    never bother using, I didn't pay much attention to the details, and
    forgot that it applies only to the declarator. I should have checked
    before posting.

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Sun Jan 11 11:49:03 2026
    From Newsgroup: comp.lang.c

    Andrey Tarasevich <noone@noone.net> writes:

    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. [...]

    Thanks Andrey. :)
    --- Synchronet 3.21a-Linux NewsLink 1.2