• function pointer question

    From Michael Sanders@porkchop@invalid.foo to comp.lang.c on Fri Jan 2 07:24:55 2026
    From Newsgroup: comp.lang.c

    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?
    --
    :wq
    Mike Sanders
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Lawrence =?iso-8859-13?q?D=FFOliveiro?=@ldo@nz.invalid to comp.lang.c on Fri Jan 2 09:04:47 2026
    From Newsgroup: comp.lang.c

    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.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Fri Jan 2 02:52:22 2026
    From Newsgroup: comp.lang.c

    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.)
    --
    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 Michael Sanders@porkchop@invalid.foo to comp.lang.c on Fri Jan 2 14:42:28 2026
    From Newsgroup: comp.lang.c

    On Fri, 2 Jan 2026 09:04:47 -0000 (UTC), Lawrence DrCOOliveiro wrote:

    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.

    Many thanks Lawrence, slow & steady, I'm gaining steam.
    --
    :wq
    Mike Sanders
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael Sanders@porkchop@invalid.foo to comp.lang.c on Fri Jan 2 14:43:42 2026
    From Newsgroup: comp.lang.c

    On Fri, 02 Jan 2026 02:52:22 -0800, Keith Thompson wrote:

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

    Excellent reply, appreciate it (especially examples).
    --
    :wq
    Mike Sanders
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael Sanders@porkchop@invalid.foo to comp.lang.c on Fri Jan 2 14:45:51 2026
    From Newsgroup: comp.lang.c

    On Fri, 2 Jan 2026 09:04:47 -0000 (UTC), Lawrence DrCOOliveiro wrote:

    C: what about ty[p]edef?

    [sic!]
    --
    :wq
    Mike Sanders
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From highcrew@high.crew3868@fastmail.com to comp.lang.c on Fri Jan 2 17:21:10 2026
    From Newsgroup: comp.lang.c

    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.

    Thanks for sharing!
    --
    High Crew
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Fri Jan 2 09:37:17 2026
    From Newsgroup: comp.lang.c

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

    I wrote the above ("It's also common..."), not Michael.

    Thanks for sharing!

    You're welcome.
    --
    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 Kaz Kylheku@046-301-5902@kylheku.com to comp.lang.c on Fri Jan 2 17:48:16 2026
    From Newsgroup: comp.lang.c

    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?

    C: what about tyedef?

    What do you think /that/ is?

    The typedef declaration is a way of introducing a name, which names a
    type, and can then be used in a declaration, or in the cast notation.
    The name is an alias for that type.

    typedef isn't a type defining mechanism; it is a mechanism for defining
    alias names for types ("typedef names" or "typedefs"). The types that
    are aliased by typedef names are specified in the ordinary declaration
    syntax, the same as like when a variable is being declared rather than a typedef name.

    Syntactically, typedef is a "storage class specifier", like "extern"
    or "static". Thus "extern typedef int x" would never make sense,
    due to storage class specifiers being exclusive.
    --
    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 James Kuyper@jameskuyper@alumni.caltech.edu to comp.lang.c on Fri Jan 2 14:03:48 2026
    From Newsgroup: comp.lang.c

    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.


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From bart@bc@freeuk.com to comp.lang.c on Fri Jan 2 19:18:56 2026
    From Newsgroup: comp.lang.c

    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.




    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael Sanders@porkchop@invalid.foo to comp.lang.c on Fri Jan 2 19:35:51 2026
    From Newsgroup: comp.lang.c

    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.
    --
    :wq
    Mike Sanders
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael Sanders@porkchop@invalid.foo to comp.lang.c on Fri Jan 2 19:41:01 2026
    From Newsgroup: comp.lang.c

    On Fri, 2 Jan 2026 14:03:48 -0500, James Kuyper wrote:

    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.

    This post is going into my notes!

    A bag of 'thank you's James.
    --
    :wq
    Mike Sanders
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Fri Jan 2 11:43:01 2026
    From Newsgroup: comp.lang.c

    bart <bc@freeuk.com> writes:
    [...]
    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'.
    [...]

    Just to clarify, the "..." refers to the list of parameter types, in
    this case "pointer to function(char [][64], int, int, const char *)
    returning void)". It's not a literal `...` token.

    I mention this because `void func(...)` is valid starting in C23; it's a variadic function with no fixed parameters. `void func(...)` is not
    compatible with, for example, `void(int)`.
    --
    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 Michael Sanders@porkchop@invalid.foo to comp.lang.c on Fri Jan 2 19:44:20 2026
    From Newsgroup: comp.lang.c

    On Fri, 2 Jan 2026 19:18:56 +0000, bart wrote:

    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.

    Yes I'm starting to see it now in my mind's eye.

    Especially: 'So a dummy 'void' return type is used.'

    These are superb answers guys =)
    --
    :wq
    Mike Sanders
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Fri Jan 2 12:07:02 2026
    From Newsgroup: comp.lang.c

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

    There were no void pointers in the original code. There were function
    pointers to functions that return void.

    A void pointer (the pointer itself) is not of the type
    it points to no? Its typeless & unknown at compile time

    No pointer is of the type it points to. `int` and `int*` are distinct
    types. (The latter is "derived" from the former.)

    A 'normal' pointer in C is a variable that stores the address
    of another variable.

    Yes, if by "pointer" you mean "pointer object". The standard also uses
    the word "pointer" for a *value* of pointer type, for example the result
    of a call to malloc().

    (The word "variable" is tricky, and the standard rarely uses it.
    "Object" is less ambiguous.)

    And *is of the type it points to*...

    No.

    char str[] = "learning publicly is a humbling experience.";
    char *ptr = str;

    And now you're introducing array-to-pointer conversion, a can of
    worms that I suggest needn't be opened at this time. A simpler
    example:

    char c = 'l';
    char *ptr = &c;

    Then void enters stage left...

    void *genericPointer;

    It can point to an integer, a character, a structure, or any other type.

    void *genericPointer = &intValue;

    "intObject" would be a better name. An object (variable you like)
    is not a value; it holds a value.

    void *genericPointer = str[];

    That's a syntax error, but it's ok if you drop the "[]".

    void* is a distinct type. Unlike (most) other object pointer types, it
    can't be dereferenced, because it doesn't specify a directly usable type
    for the object it points to.

    Given:

    int n = 42;
    int *ip = &n;
    void *vp = &n;

    ip points to n. vp, in a sense, does not, but converting vp
    from void* to int* yields a pointer value that does point to n.
    A void* holds something you can think of as a raw untyped address.
    It doesn't point to anything, but converting it to foo* gives you
    a pointer to an object of type foo.

    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.

    "void" is an incomplete type that cannot be completed.
    "void*" is a pointer type with some special characteristics.

    First, a value of any object pointer type can be converted to void* and
    back again, yielding the original pointer value. This is not guaranteed
    for (most) other pointer types. For example, converting a char* value
    to int* can lose information. (On most real-world systems, all object pointers, and often all function pointers as well, have the same representation, but the standard doesn't guarantee this.)

    Second, conversions between void* and other object pointer types are
    often performed implicitly. You can have an object pointer on one
    side of an assignment and a void* pointer on the other side. On some
    (exotic) systems, this conversion might do something more than just
    copying the representation.

    The character pointer types char*, signed char*, and unsigned char*
    share the first characteristic, but not the second. (In very early
    (pre-ANSI) C, char* was commonly used as a generic pointer type before
    void and void* were introduced.)

    Every pointer value, object, or expression has a well defined type, specifically a pointer type. That type specifies the type of
    the object it points to -- unless the pointer is of type void*.
    (Or it can point to a function; object pointers and function pointers
    are distinct.)

    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.
    --
    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 Kaz Kylheku@046-301-5902@kylheku.com to comp.lang.c on Fri Jan 2 21:50:06 2026
    From Newsgroup: comp.lang.c

    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.

    Pre-ANSI "classic" C didn't have "void". It was invented in C++ in the 1980s.

    In classic C, if you didn't want a function to return anything,
    you just omitted the type specifier for the function definition:

    foo() { }

    The function's type was still "function returning int". You had the of
    the function would avoid trying to use the return value, and then
    omitted returning one in the function. It was a "gentlemen's agreement"
    for simulating procedures using functions.

    This did not sit well with C++. C++ people wanted this gentlemen's
    agreement codified properly into the type system, while remaining C
    compatible. So the they introduced the void type and keyword.

    Once void was a type specifier keyword in C++, then of course that
    allowed declarators to create derived types, leading to the discovery
    of "void *" (pointer to void). C++ decided to use it as a generic
    pointer to any object, with the rule that other pointer-to-object
    types could implicitly convert to void *, but not vice versa.

    The ANSI C committe adopted void from C++, probably not just for
    compatibility but because it jived with the stronger typing they
    were introducing, including prototypes. (Also basically from C++?)

    I seem to recall it was ANSI C that invented the syntactic hack of
    (void) as a parameter list, because the () parameter list in C
    meant "nothing is specified about the parameters" and that had to remain
    for compatibility. So the (void) parameter list was adopted as denoting
    "this function takes no parameters".

    That hack was then adopted back into C++. In C++, () already meant
    "this function is prototyped as having no parameters", but for
    compatibility with ANSI C prototypes, C++ adopted (void) as a synonym
    for that.

    We have now gone full circle. () in C now means the same thing as in
    C++, and (void) is now a historic quirk, required only for compatibility
    with existing code and all the historic dialects which required it.

    ANSI C adopted "void *" from C++ also, but in a slightly different
    way, allowing implicit conversion botrh ways between pointer-to-object
    types and pointer to void.

    A void pointer (the pointer itself) is not of the type
    it points to no? Its typeless & unknown at compile time

    It is not typeless; it is a pointer to the type void. void is an object
    type, but incomplete; in that it is similar to something like "struct
    foo" in the absence of another declaration that completes the foo struct
    type.

    Incomplete types cannot be used to access an object,
    nor to define an object.

    Since no object can be of type void, we know that a given void *, if it
    points to an object, does not have that object's correct type.
    It only has the correct address.

    The void * type, along with the "{signed | unsigned | <nothing> } char *" type, is "blessed" by the language standard as being able to represent the
    address of any object: a pointer to any object type can be converted to
    void * such that it can then be converted back to that original type, recovering the original pointer.

    A 'normal' pointer in C is a variable that stores the address
    of another variable. And *is of the type it points to*...

    That's right; and if that's the only pointer type we have, then
    we have a problem: we cannot write a function like memcpy
    for copying any type of object. I mean, we cannot even write its
    prototype.

    There are ways around it with complicated mechanisms for generics,
    like C++ templates.

    One way to achieve generic code in the middle of a static type system is
    to have escape hatches to get around the type system, at the cost of
    safety.

    Safety is the property of the language assuring correct operation
    by rejecting incorrect situations in programs (either prior to run time
    or at least at run time).

    When we abandon safety, then assuring correct operation is left
    to the programmer; the language translator or run-time is not required
    to identify and reject incorrect situations.

    With void pointers, we can create a data type such as a linked list
    containing any objects (by reference) and even integers (by conversion
    to void * and back).

    The nodes of the list are declared as having an member of type "void *",
    which holds the element datum.

    The program must ensure that whatever values are stored into the list,
    they are converted back to the original type when they are accessed.

    This is not safe; it is possible to have a mistake whereby a list
    of Widget objects made int he Widget module is accidentally passed
    into a function in the Gadget module which requires a list of Gadget
    objects. That function retrieves the void * element values,
    converts them to Gadget * and "hilarity ensues".

    But, if you can avoid this kind of mistake, it is a very
    easy-to-understand mechanism for generic programming, requiring
    uncomplicated language support, and doesn't require all safety to be
    abandoned.


    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.

    It survives the lack of tracking the original type.

    Nothing undefdined has happened so far, because genericPointer is
    required to be capable of representing the address given by the pointer-to-object type it is being initialized with.

    Furthermore, you cannot use genericPointer directly to access anything.

    You can't say "*genericPointer = 42.0" such that a string is clobbered
    with a floating-point representation.

    Where you leave type safety is when you do this:

    double *doublePtr = genericPointer;

    Now you can do *doublePtr = 42.0.

    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

    void has multiple semantics loaded onto it:

    - Empty parameter list (ANSI C origin): (void)
    - Function returning no value: void fn(...)
    - Value of expression discarded by cast to void: (void) expr
    - Pointer to any object: void *

    These are all different and best regarded as unrelated.
    Just like "static" has multiple meanings.

    For instance, in a function returhning no value, you cannot
    return a void value!

    void fn(void)
    {
    return (void) 42;
    }

    Even though at first glance it appears that the type of the expression
    "(void) 42" is compatible with the return type of void, that's not
    how it works. The function returns nothing, and that nothing has no
    type at all. In a function returning nothing, it is a diagnosable
    constraint violation to have a return statement with an expression.
    Only "return;" may appear.

    Basically the language (or languages: both C and C++, in all their
    current and historic dialects) play a game with void. There is a void
    keyword and a void type, and the semantics is crafted to fit certain
    contextual situations, in utilitarian ways.
    --
    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 Fri Jan 2 21:52:43 2026
    From Newsgroup: comp.lang.c

    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.

    Pre-ANSI "classic" C didn't have "void". It was invented in C++ in the 1980s.

    In classic C, if you didn't want a function to return anything,
    you just omitted the type specifier for the function definition:

    foo() { }

    The function's type was still "function returning int". You had the of
    the function would avoid trying to use the return value, and then
    omitted returning one in the function. It was a "gentlemen's agreement"
    for simulating procedures using functions.

    This did not sit well with C++. C++ people wanted this gentlemen's
    agreement codified properly into the type system, while remaining C
    compatible. So the they introduced the void type and keyword.

    Once void was a type specifier keyword in C++, then of course that
    allowed declarators to create derived types, leading to the discovery
    of "void *" (pointer to void). C++ decided to use it as a generic
    pointer to any object, with the rule that other pointer-to-object
    types could implicitly convert to void *, but not vice versa.

    The ANSI C committe adopted void from C++, probably not just for
    compatibility but because it jived with the stronger typing they
    were introducing, including prototypes. (Also basically from C++?)

    I seem to recall it was ANSI C that invented the syntactic hack of
    (void) as a parameter list, because the () parameter list in C
    meant "nothing is specified about the parameters" and that had to remain
    for compatibility. So the (void) parameter list was adopted as denoting
    "this function takes no parameters".

    That hack was then adopted back into C++. In C++, () already meant
    "this function is prototyped as having no parameters", but for
    compatibility with ANSI C prototypes, C++ adopted (void) as a synonym
    for that.

    We have now gone full circle. () in C now means the same thing as in
    C++, and (void) is now a historic quirk, required only for compatibility
    with existing code and all the historic dialects which required it.

    ANSI C adopted "void *" from C++ also, but in a slightly different
    way, allowing implicit conversion botrh ways between pointer-to-object
    types and pointer to void.

    A void pointer (the pointer itself) is not of the type
    it points to no? Its typeless & unknown at compile time

    It is not typeless; it is a pointer to the type void. void is an object
    type, but incomplete; in that it is similar to something like "struct
    foo" in the absence of another declaration that completes the foo struct
    type.

    Incomplete types cannot be used to access an object,
    nor to define an object.

    Since no object can be of type void, we know that a given void *, if it
    points to an object, does not have that object's correct type.
    It only has the correct address.

    The void * type, along with the "{signed | unsigned | <nothing> } char *" type, is "blessed" by the language standard as being able to represent the
    address of any object: a pointer to any object type can be converted to
    void * such that it can then be converted back to that original type, recovering the original pointer.

    A 'normal' pointer in C is a variable that stores the address
    of another variable. And *is of the type it points to*...

    That's right; and if that's the only pointer type we have, then
    we have a problem: we cannot write a function like memcpy
    for copying any type of object. I mean, we cannot even write its
    prototype.

    There are ways around it with complicated mechanisms for generics,
    like C++ templates.

    One way to achieve generic code in the middle of a static type system is
    to have escape hatches to get around the type system, at the cost of
    safety.

    Safety is the property of the language assuring correct operation
    by rejecting incorrect situations in programs (either prior to run time
    or at least at run time).

    When we abandon safety, then assuring correct operation is left
    to the programmer; the language translator or run-time is not required
    to identify and reject incorrect situations.

    With void pointers, we can create a data type such as a linked list
    containing any objects (by reference) and even integers (by conversion
    to void * and back).

    The nodes of the list are declared as having an member of type "void *",
    which holds the element datum.

    The program must ensure that whatever values are stored into the list,
    they are converted back to the original type when they are accessed.

    This is not safe; it is possible to have a mistake whereby a list
    of Widget objects made int he Widget module is accidentally passed
    into a function in the Gadget module which requires a list of Gadget
    objects. That function retrieves the void * element values,
    converts them to Gadget * and "hilarity ensues".

    But, if you can avoid this kind of mistake, it is a very
    easy-to-understand mechanism for generic programming, requiring
    uncomplicated language support, and doesn't require all safety to be
    abandoned.


    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.

    It survives the lack of tracking the original type.

    Nothing undefdined has happened so far, because genericPointer is
    required to be capable of representing the address given by the pointer-to-object type it is being initialized with.

    Furthermore, you cannot use genericPointer directly to access anything.

    You can't say "*genericPointer = 42.0" such that a string is clobbered
    with a floating-point representation.

    Where you leave type safety is when you do this:

    double *doublePtr = genericPointer;

    Now you can do *doublePtr = 42.0.

    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

    void has multiple semantics loaded onto it:

    - Empty parameter list (ANSI C origin): (void)
    - Function returning no value: void fn(...)
    - Value of expression discarded by cast to void: (void) expr
    - Pointer to any object: void *

    These are all different and best regarded as unrelated.
    Just like "static" has multiple meanings.

    For instance, in a function returhning no value, you cannot
    return a void value!

    void fn(void)
    {
    return (void) 42;
    }

    Even though at first glance it appears that the type of the expression
    "(void) 42" is compatible with the return type of void, that's not
    how it works. The function returns nothing, and that nothing has no
    type at all. In a function returning nothing, it is a diagnosable
    constraint violation to have a return statement with an expression.
    Only "return;" may appear.

    Basically the language (or languages: both C and C++, in all their
    current and historic dialects) play a game with void. There is a void
    keyword and a void type, and the semantics is crafted to fit certain
    contextual situations, in utilitarian ways.

    It's like certain words in English, like prepositions.

    Sometimes "by" means next to, like "stand by me".

    Sometimes it denotes agency: "pass by value".

    Sometimes an upper temporal limit: "by the time you get there".

    If you're a learner of the language and all you know so far is the "by"
    of "stand by me", you are confused by "by the same time tomorrow".
    --
    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 Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.lang.c on Fri Jan 2 14:18:07 2026
    From Newsgroup: comp.lang.c

    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.

    typedef void (*generic_func_ptr)(void)
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Ben Bacarisse@ben@bsb.me.uk to comp.lang.c on Sat Jan 3 03:33:54 2026
    From Newsgroup: comp.lang.c

    highcrew <high.crew3868@fastmail.com> writes:

    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.

    Declarations using the function type do often make sense. Note
    "declarations" -- you can't use the typedef'd name to /define/ a function
    of the given type.

    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 }
    };
    --
    Ben.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael Sanders@porkchop@invalid.foo to comp.lang.c on Sat Jan 3 06:06:09 2026
    From Newsgroup: comp.lang.c

    On Fri, 02 Jan 2026 12:07:02 -0800, Keith Thompson wrote:

    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.

    Sure enough, plan on reading that Monday. Thanks.
    --
    :wq
    Mike Sanders
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael Sanders@porkchop@invalid.foo to comp.lang.c on Sat Jan 3 06:08:40 2026
    From Newsgroup: comp.lang.c

    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.
    --
    :wq
    Mike Sanders
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Sat Jan 3 13:55:01 2026
    From Newsgroup: comp.lang.c

    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.

    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.

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andrey Tarasevich@noone@noone.net to comp.lang.c on Sat Jan 3 07:41:33 2026
    From Newsgroup: comp.lang.c

    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;

    is already a proper bunch of non-defining function declarations.
    `extern` does not make it more "non-defining" than it already is.
    --
    Best regards,
    Andrey.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.lang.c on Sat Jan 3 12:04:47 2026
    From Newsgroup: comp.lang.c

    On 1/3/2026 4:55 AM, David Brown 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
    -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.

    Oh, I missed that. Sorry everybody.


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

    Indeed.


    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.


    Agreed. Fwiw, here is some of my old code exploring some related things:


    /* Interfaces ____________________________________________________________________*/
    #include <stddef.h>


    struct object_prv_vtable {
    int (*fp_destroy) (void* const);
    };


    struct device_prv_vtable {
    int (*fp_read) (void* const, void*, size_t);
    int (*fp_write) (void* const, void const*, size_t);
    };


    struct device_vtable {
    struct object_prv_vtable const object;
    struct device_prv_vtable const device;
    };


    struct device {
    struct device_vtable const* vtable;
    };


    #define object_destroy(mp_self) ( \
    (mp_self)->vtable->object.fp_destroy((mp_self)) \
    )


    #define device_read(mp_self, mp_buf, mp_size) ( \
    (mp_self)->vtable->device.fp_read((mp_self), (mp_buf), (mp_size)) \
    )


    #define device_write(mp_self, mp_buf, mp_size) ( \
    (mp_self)->vtable->device.fp_write((mp_self), (mp_buf), (mp_size)) \
    )






    /* Sample Header (usb_drive.h) ____________________________________________________________________*/
    #if ! defined(USB_HEADER_H)
    #define USB_HEADER_H


    extern int usb_drive_create(struct device** const);


    #endif







    /* Sample Impl (usb_drive.c) ____________________________________________________________________*/
    /* #include "usb_drive.c" */
    #include <stdio.h>
    #include <stdlib.h>


    struct usb_drive {
    struct device device;
    /* whatever */
    };


    static int usb_drive_object_destroy(void* const);
    static int usb_drive_device_read(void* const, void*, size_t);
    static int usb_drive_device_write(void* const, void const*, size_t);


    static struct device_vtable const g_table = {
    { /* object */
    usb_drive_object_destroy
    },

    { /* device */
    usb_drive_device_read,
    usb_drive_device_write
    }
    };


    int usb_drive_create(
    struct device** const pself
    ) {
    struct usb_drive* const self = malloc(sizeof(*self));
    if (self) {
    self->device.vtable = &g_table;
    *pself = &self->device;
    return 0;
    }
    return -1;
    }


    int usb_drive_object_destroy(
    void* const self_
    ) {
    struct usb_drive* const self = self_;
    printf("usb_drive_object_destroy(%p)\n", (void*)self);
    free(self_);
    return 0;
    }


    int usb_drive_device_read(
    void* const self_,
    void* buf,
    size_t size
    ) {
    struct usb_drive* const self = self_;
    printf("usb_drive_device_read(%p, %p, %lu)\n",
    (void*)self, buf, (unsigned long)size);
    return 0;
    }


    int usb_drive_device_write(
    void* const self_,
    void const* buf,
    size_t size
    ) {
    struct usb_drive* const self = self_;
    printf("usb_drive_device_write(%p, %p, %lu)\n",
    (void*)self, buf, (unsigned long)size);
    return 0;
    }







    /* Sample Application ____________________________________________________________________*/
    void read_write(
    struct device* const self
    ) {
    char buf[100];

    device_read(self, buf, 50);

    device_write(self, buf, 5);
    }


    int main(void) {
    struct device* a_device;

    if (! usb_drive_create(&a_device)) {
    read_write(a_device);

    object_destroy(a_device);
    }

    return 0;
    }

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andrey Tarasevich@noone@noone.net to comp.lang.c on Sat Jan 3 13:01:07 2026
    From Newsgroup: comp.lang.c

    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.

    2a. Pre-C17: Top-level qualifiers on function return type are preserved
    as part of function type. They are NOT ignored when determining function
    type compatibility, i.e. they have to match exactly for the types to be compatible.

    2b. Post-C17: Top-level qualifiers on function return type are not
    included into the function type. (Function type compatibility rules
    remain unchanged: they still formally require a match, but now it's moot.)

    For example, the following intializations are valid

    int foo(const int);
    int bar(int);
    ...
    int (*pa)(int) = &foo;
    int (*pb)(const int) = &bar;

    Note: they function types do not match in this case, yet they are
    compatible. And that's all that's needed.

    Meanwhile, the following initializations are invalid in pre-C17 version
    of the language

    const int baz(void);
    int qux(void);
    ...
    int (*pc)(void) = &baz;
    const int (*pd)(void) = &qux;

    They become valid in C17 and later.

    (Interestingly enough, Clang keeps complaining about them even in
    `-std=c17` mode.)
    --
    Best regards,
    Andrey
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Lawrence =?iso-8859-13?q?D=FFOliveiro?=@ldo@nz.invalid to comp.lang.c on Sat Jan 3 21:46:07 2026
    From Newsgroup: comp.lang.c

    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.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Lawrence =?iso-8859-13?q?D=FFOliveiro?=@ldo@nz.invalid to comp.lang.c on Sat Jan 3 22:05:13 2026
    From Newsgroup: comp.lang.c

    On Sat, 3 Jan 2026 12:04:47 -0800, Chris M. Thomasson wrote:

    struct object_prv_vtable {
    int (*fp_destroy) (void* const);
    };

    ...

    (mp_self)->vtable->object.fp_destroy((mp_self)) \

    which is easier to write than

    (*(mp_self)->vtable->object.fp_destroy)((mp_self))

    , donrCOt you think? (And similarly for other cases in that code.)

    Look on it as a fudge allowed by C to get around the awkwardness of
    its inside-out type definitions ...
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From James Kuyper@jameskuyper@alumni.caltech.edu to comp.lang.c on Sat Jan 3 17:20:16 2026
    From Newsgroup: comp.lang.c

    On 2026-01-02 14:35, Michael Sanders wrote:
    On Fri, 2 Jan 2026 17:48:16 -0000 (UTC), Kaz Kylheku wrote:

    On 2026-01-02, Michael Sanders <porkchop@invalid.foo> wrote:
    [restore snippage]
    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.

    The pointer named render should be declared with 'void' at the beginning because moo and mastermind are declared with a 'void' at the beginning.

    Consider the following alternative:

    int imoo(char HISTORY[][64], int hst_len, int invalid, const char *gme_msg)
    int imastermind(char HISTORY[][64], int hst_len, int invalid, const char *gme_msg)

    int (*irender)(char [][64], int, int, const char *) = MOO ? imoo :
    imastermind;

    irender requires 'int' at the beginning because imoo and imastermind
    have 'int' at the beginning. That is the return type of the function.

    The only difference is that 'void' is, in this context, a special
    keyword indicating that the function does not return a value. Since the function has no return type, it's convenient to insert 'void' in the
    same location that would otherwise have contained the type of the value returned.

    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.

    The third meaning for 'void' is a special meaning for pointers: "void
    *p" declares a pointer that can store any pointer to an object.


    A void pointer (the pointer itself) is not of the type
    it points to no? ...

    Of course not. In general, if the type of the thing pointed at is "X",
    the type of the pointer itself is "pointer to X". However, a void
    pointer has the type "pointer to void". Note: there are no void pointers
    in your code. The pointer named render has the type "pointer to a
    function returning void with arguments of the types ...".

    ... Its typeless & unknown at compile time

    No, it very definitely has a type, "pointer to void", and if that type
    were not known at compile time, the compiler wouldn't know what to do
    with it. Because it does know the type, it can take the following code:

    int i;
    void *p = &i;

    and translate it into machine language that creates a pointer to an int
    which points at 'i', and converts it into a pointer to void (on many
    systems, that conversion is a no-op, but on some machines pointers to
    different types can have different representations) before storing it in
    'p'.

    A 'normal' pointer in C is a variable that stores the address
    of another variable. And *is of the type it points to*...

    No, it does not. "pointer to X" is a different type from "X".

    char str[] = "learning publicly is a humbling experience.";
    123456789012345678901234567890
    char *ptr = str;

    In this case, str has the type "char[


    Then void enters stage left...

    void *genericPointer;

    It can point to an integer, a character, a structure, or any other type.

    It can only point at object types. It cannot point at function types.

    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.
    void* is a pointer type that can point at objects of any type. However,
    it must be converted back to a pointer to the appropriate type before
    the converted pointer can be used to access the object.

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Sat Jan 3 16:39:14 2026
    From Newsgroup: comp.lang.c

    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.
    --
    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 Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Sat Jan 3 16:48:08 2026
    From Newsgroup: comp.lang.c

    James Kuyper <jameskuyper@alumni.caltech.edu> writes:
    [...]
    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.
    [...]

    Some of this changed in C23. I don't have all the details off the top
    of my head, but as I understand it old-style function definitions
    are no longer allowed and `int foo()` is equivalent to `int foo(void)`.

    (Which means, I think, that my argument that `int main()` has undefined behavior does not apply in C23.)

    It's still not a bad idea to write (void) on a parameterless function declaration/definition unless you're sure your code will be compiled
    only with a compiler that implements C23 or later.

    The 2023 ISO C standard is expensive, but the closest draft is:

    https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3220.pdf
    --
    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 David Brown@david.brown@hesbynett.no to comp.lang.c on Sun Jan 4 12:03:20 2026
    From Newsgroup: comp.lang.c

    On 03/01/2026 22:46, Lawrence DrCOOliveiro wrote:
    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.

    It is redundant here to the compiler, but might still be helpful to
    human readers.

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Sun Jan 4 12:15:29 2026
    From Newsgroup: comp.lang.c

    On 04/01/2026 01:39, Keith Thompson wrote:
    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.


    Yes.

    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.


    Yes. This also means there is no generic function pointer type
    equivalent to "void *" for object pointers, since there is no one
    function pointer type that stands out, no special case, and no common agreement on the type. Some people will use "void (*)(void)", some will
    use "int (*)(int)", some "int (*)()", and so on. That is another sense
    in which there is no function pointer type equivalent to "void *".

    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.


    The dummy struct idea is nice - it could help avoid accidents.


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael Sanders@porkchop@invalid.foo to comp.lang.c on Mon Jan 5 08:39:53 2026
    From Newsgroup: comp.lang.c

    On Sat, 3 Jan 2026 17:20:16 -0500, James Kuyper wrote:

    [...]

    James, earnest thanks for the time you've spent in your
    reply I /really/ appreciate it (Keith & others too).

    I'm going to dig in on the void issue & get a grip on
    understanding it better. I might have questions down the
    road & am thankful for everyone's kindness & forbearance.

    Must run (short handed at work).
    --
    :wq
    Mike Sanders
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From James Kuyper@jameskuyper@alumni.caltech.edu to comp.lang.c on Mon Jan 5 06:47:37 2026
    From Newsgroup: comp.lang.c

    On 2026-01-03 17:20, James Kuyper wrote:
    ...
    char str[] = "learning publicly is a humbling experience.";
    12345678901234567890\
    char *ptr = str;
    In this case, str has the type "char[

    I got interrupted while composing that message, and sent it with that
    part incomplete. It should have said "has the type char[44]", and I had intended to remove the digits I was typing to confirm the length of the
    string.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.lang.c on Mon Jan 5 12:40:08 2026
    From Newsgroup: comp.lang.c

    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!


    But seriously, thanks for the detailed
    reply. I move forward day-by-day.


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

    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.
    --
    :wq
    Mike Sanders
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael Sanders@porkchop@invalid.foo to comp.lang.c on Tue Jan 6 12:32:43 2026
    From Newsgroup: comp.lang.c

    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?
    --
    :wq
    Mike Sanders
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From highcrew@high.crew3868@fastmail.com to comp.lang.c on Tue Jan 6 13:59:20 2026
    From Newsgroup: comp.lang.c

    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.
    --
    High Crew
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • 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:

    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?
    --- 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:

    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?

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

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

    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:

    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?


    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:

    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?

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

    "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,

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


    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