• Epoch seconds and linear count (was Re: printf and time_t)

    From Janis Papanagnou@janis_papanagnou+ng@hotmail.com to comp.lang.c on Tue Jan 6 14:51:52 2026
    From Newsgroup: comp.lang.c

    On 2026-01-05 23:41, Keith Thompson wrote:
    James Kuyper <jameskuyper@alumni.caltech.edu> writes:
    On 2026-01-05 11:34, David Brown wrote:
    ...
    As I understand it, time_t is intended to be suitable for holding a
    number of seconds ...

    The standard says nothing about that.

    ... (it is used for that purpose in struct timespec). ...

    The standard says nothing to connect time_t to struct timespec.

    Yes, but also no.

    struct timespec contains at least the following members, in any order:

    time_t tv_sec; // whole seconds — ≥ 0
    long tv_nsec; // nanoseconds — [0, 999999999]

    But a footnote says:

    The tv_sec member is a linear count of seconds and may not have
    the normal semantics of a time_t.

    This makes for a simpler implementation *if* the time_t value
    returned by time(), and operated on by difftime(), mktime(), ctime(), gmtime(), and localtime(), happens to hold a linear count of seconds
    (as it does on most systems).

    Hmm.. - my post is not aiming in your direction but this statement
    made me curious. - A linear count (in the sense of TAI) should then
    show a difference between civil time and [TAI-]seconds since Epoch.

    A quick test shows that there's no leap seconds considered. So no
    "linear count" (in the sense of TAI). Leap seconds are disregarded
    in the "linear" seconds count.


    It's odd that time_t is used for two potentially very different
    purposes, one as a member of struct timespec and another as used
    in all other contexts.

    Semantically it might have made sense if its used for differentiating
    internal TAI (time_t) from UTC or local time on the UI (struct tm).

    Janis

    And I would guess that a lot of code that
    uses struct timespec *assumes* that its time_t member has the same
    semantics value returned by time(NULL).

    For example, as I write this the time is 2026-01-05 22:32:57.881 UTC.
    The corresponding value returned by time() is 1767652377 (seconds
    since the 1970 epoch, no milliseconds). An implementation could
    represent the current time (the value returned by time(NULL) as a
    64-bit integer with the value 20260105223257881. But timespec_get()
    would still have to set the tv_sec member to 1767652377.

    It might have been cleaner either to require that time_t represents a
    count of seconds, or to use a type other than time_t for the tv_sec
    member of struct timespec.

    I know there are systems that use something other than seconds
    since 1970 in the underlying time representation, but are there any
    C implementations that don't use seconds since 1970? (POSIX and
    Windows both specify that.)


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From James Kuyper@jameskuyper@alumni.caltech.edu to comp.lang.c on Tue Jan 6 10:22:28 2026
    From Newsgroup: comp.lang.c

    On 2026-01-05 19:22, Lawrence D’Oliveiro wrote:
    On Mon, 5 Jan 2026 16:23:09 -0000 (UTC), Lew Pitcher wrote:

    As Andrey pointed out, time_t can resolve to a floatingpoint type,

    POSIX says time_t is an integer type <https://manpages.debian.org/time_t(3type)>.

    Which means that implementations where time_t has a floating point type
    cannot comply with POSIX; many implementations fail to do so.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From James Kuyper@jameskuyper@alumni.caltech.edu to comp.lang.c on Tue Jan 6 10:31:41 2026
    From Newsgroup: comp.lang.c

    On 2026-01-06 04:29, Michael S wrote:
    On Tue, 6 Jan 2026 00:27:04 -0000 (UTC)
    Lawrence D’Oliveiro <ldo@nz.invalid> wrote:
    ...
    Section 7.8 of the C spec defines macros you can use so you don’t have
    to hard-code assumptions about the lengths of integers in
    printf-format strings.

    Did you ever try to use them? They look ugly.

    Which is more important, correctness or beauty?

    If you know that an expression has one of the standard-named types or
    typedefs for with there is a corresponding printf() specifier, you
    should use that specifier. Otherwise, if you know that an expression has
    one of the types declared in <stdint.h>, you should use the
    corresponding macro #defined in <inttypes.h> to print it. If you have a
    value that is not known to be of one of those types, but is known to be convertible to one of those types without change of value, you should
    convert it to one of those types.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Lew Pitcher@lew.pitcher@digitalfreehold.ca to comp.lang.c on Tue Jan 6 15:45:37 2026
    From Newsgroup: comp.lang.c

    On Tue, 06 Jan 2026 10:22:28 -0500, James Kuyper wrote:

    On 2026-01-05 19:22, Lawrence D’Oliveiro wrote:
    On Mon, 5 Jan 2026 16:23:09 -0000 (UTC), Lew Pitcher wrote:

    As Andrey pointed out, time_t can resolve to a floatingpoint type,

    POSIX says time_t is an integer type

    Actually, POSIX does not say that.

    <https://manpages.debian.org/time_t(3type)>.

    The Debian manpages do not necessarily represent current POSIX standards.

    The current online POSIX standards pages say that, among others, time_t
    "shall be defined as arithmetic types of an appropriate length" (https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_types.h.html)

    The current POSIX definition of time(2) does mention integer values, but
    only as historic reference, and refers to the definition of time_t in
    <time.h> (https://pubs.opengroup.org/onlinepubs/9799919799/functions/time.html)

    The current POSIX definition <time.h> says that the <time.h> header
    "shall define the clock_t, size_t, time_t, types as described in <sys/types.h>."
    (https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/time.h.html)

    And, the current POSIX definition of the <sys/types.h> header says, as above, that time_t shall be defined as an arithmetic type.

    Which means that implementations where time_t has a floating point type cannot comply with POSIX; many implementations fail to do so.

    In practice, the POSIX specs likely means that time_t will represent an
    integer of some size, but the POSIX specs do /not/ say that time_t /must/
    be an integer.
    --
    Lew Pitcher
    "In Skills We Trust"
    Not LLM output - I'm just like this.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael =?ISO-8859-1?Q?B=E4uerle?=@michael.baeuerle@gmx.net to comp.lang.c on Tue Jan 6 17:00:43 2026
    From Newsgroup: comp.lang.c

    Lew Pitcher wrote:
    On Tue, 06 Jan 2026 10:22:28 -0500, James Kuyper wrote:
    On 2026-01-05 19:22, Lawrence D’Oliveiro wrote:
    On Mon, 5 Jan 2026 16:23:09 -0000 (UTC), Lew Pitcher wrote:

    As Andrey pointed out, time_t can resolve to a floatingpoint type,

    POSIX says time_t is an integer type

    Actually, POSIX does not say that.

    <https://manpages.debian.org/time_t(3type)>.

    The Debian manpages do not necessarily represent current POSIX standards.

    The current online POSIX standards pages say that, among others, time_t "shall be defined as arithmetic types of an appropriate length" (https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_types.h.html)

    Looks like you looked at an old version. Currently there is:
    |
    | [CX] time_t shall be an integer type with a width (see <stdint.h>) of at least 64 bits.
    | [...]
    | Austin Group Defect 1462 is applied, changing time_t to have a width of at least 64 bits.

    It refers to this issue:
    <https://www.austingroupbugs.net/view.php?id=1462>
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Lew Pitcher@lew.pitcher@digitalfreehold.ca to comp.lang.c on Tue Jan 6 16:08:44 2026
    From Newsgroup: comp.lang.c

    On Tue, 06 Jan 2026 17:00:43 +0100, Michael Bäuerle wrote:

    Lew Pitcher wrote:
    On Tue, 06 Jan 2026 10:22:28 -0500, James Kuyper wrote:
    On 2026-01-05 19:22, Lawrence D’Oliveiro wrote:
    On Mon, 5 Jan 2026 16:23:09 -0000 (UTC), Lew Pitcher wrote:

    As Andrey pointed out, time_t can resolve to a floatingpoint type,

    POSIX says time_t is an integer type

    Actually, POSIX does not say that.

    <https://manpages.debian.org/time_t(3type)>.

    The Debian manpages do not necessarily represent current POSIX standards.

    The current online POSIX standards pages say that, among others, time_t
    "shall be defined as arithmetic types of an appropriate length"
    (https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_types.h.html)

    Looks like you looked at an old version. Currently there is:
    |
    | [CX] time_t shall be an integer type with a width (see <stdint.h>) of at least 64 bits.
    | [...]
    | Austin Group Defect 1462 is applied, changing time_t to have a width of at least 64 bits.

    Do you have a URL reference for this? I got my POSIX references direct from the Open Group's
    "current standards" links. The time(2), <time.h> and <sys/types.h> webpages are all
    headed:
    The Open Group Base Specifications Issue 8
    IEEE Std 1003.1-2024

    Perhaps they have not yet applied the defect remediation to the online reference.

    It refers to this issue:
    <https://www.austingroupbugs.net/view.php?id=1462>
    --
    Lew Pitcher
    "In Skills We Trust"
    Not LLM output - I'm just like this.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Lew Pitcher@lew.pitcher@digitalfreehold.ca to comp.lang.c on Tue Jan 6 16:18:39 2026
    From Newsgroup: comp.lang.c

    On Tue, 06 Jan 2026 17:00:43 +0100, Michael Bäuerle wrote:

    Lew Pitcher wrote:
    On Tue, 06 Jan 2026 10:22:28 -0500, James Kuyper wrote:
    On 2026-01-05 19:22, Lawrence D’Oliveiro wrote:
    On Mon, 5 Jan 2026 16:23:09 -0000 (UTC), Lew Pitcher wrote:

    As Andrey pointed out, time_t can resolve to a floatingpoint type,

    POSIX says time_t is an integer type

    Actually, POSIX does not say that.

    <https://manpages.debian.org/time_t(3type)>.

    The Debian manpages do not necessarily represent current POSIX standards.

    The current online POSIX standards pages say that, among others, time_t
    "shall be defined as arithmetic types of an appropriate length"
    (https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_types.h.html)

    Looks like you looked at an old version. Currently there is:
    |
    | [CX] time_t shall be an integer type with a width (see <stdint.h>) of at least 64 bits.
    | [...]
    | Austin Group Defect 1462 is applied, changing time_t to have a width of at least 64 bits.

    It refers to this issue:
    <https://www.austingroupbugs.net/view.php?id=1462>

    Ok, I've looked at the bugreport, and it looks like they /have/ updated the online documentation, as per the bug resolution. I missed the crucial part
    in <sys/types.h> that you quoted.

    Sorry. I'll be quiet now.
    --
    Lew Pitcher
    "In Skills We Trust"
    Not LLM output - I'm just like this.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From James Kuyper@jameskuyper@alumni.caltech.edu to comp.lang.c on Tue Jan 6 11:19:14 2026
    From Newsgroup: comp.lang.c

    On 2026-01-06 11:08, Lew Pitcher wrote:
    On Tue, 06 Jan 2026 17:00:43 +0100, Michael Bäuerle wrote:

    Lew Pitcher wrote:
    ...
    The current online POSIX standards pages say that, among others, time_t
    "shall be defined as arithmetic types of an appropriate length"
    (https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_types.h.html)

    I followed the link Lew provided, and found precisely the text quoted by Michael:

    Looks like you looked at an old version. Currently there is:
    |
    | [CX] time_t shall be an integer type with a width (see <stdint.h>) of at least 64 bits.
    | [...]
    | Austin Group Defect 1462 is applied, changing time_t to have a width of at least 64 bits.

    The part where it says that time_t shall be an integer type is in a
    separate section titled "Additionally", 27 lines of text after it says
    "shall be defined as arithmetic types ...".

    Do you have a URL reference for this? I got my POSIX references direct from the Open Group's
    "current standards" links. The time(2), <time.h> and <sys/types.h> webpages are all
    headed:
    The Open Group Base Specifications Issue 8
    IEEE Std 1003.1-2024

    Perhaps they have not yet applied the defect remediation to the online reference.


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael S@already5chosen@yahoo.com to comp.lang.c on Tue Jan 6 20:05:22 2026
    From Newsgroup: comp.lang.c

    On Tue, 6 Jan 2026 10:31:41 -0500
    James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
    On 2026-01-06 04:29, Michael S wrote:
    On Tue, 6 Jan 2026 00:27:04 -0000 (UTC)
    Lawrence D’Oliveiro <ldo@nz.invalid> wrote:
    ...
    Section 7.8 of the C spec defines macros you can use so you don’t
    have to hard-code assumptions about the lengths of integers in
    printf-format strings.

    Did you ever try to use them? They look ugly.

    Which is more important, correctness or beauty?

    It depends.
    When I know for sure that incorrectness has no consequences, like
    in case of using %u to print 'unsigned long' on target with 32-bit
    longs, or like using %llu to print 'unsigned long' on target with
    64-bit longs, then beauty wins. Easily.
    If you know that an expression has one of the standard-named types or typedefs for with there is a corresponding printf() specifier, you
    should use that specifier. Otherwise, if you know that an expression
    has one of the types declared in <stdint.h>, you should use the
    corresponding macro #defined in <inttypes.h> to print it.
    I should? Really?
    Sorry, James, but you have no authority to make such statements.
    Unless you meant colloquial 'you' rather than a person participating on
    Usenet under nick Michael_S.
    If you have
    a value that is not known to be of one of those types, but is known
    to be convertible to one of those types without change of value, you
    should convert it to one of those types.
    That is better advice.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From scott@scott@slp53.sl.home (Scott Lurndal) to comp.lang.c on Tue Jan 6 18:16:51 2026
    From Newsgroup: comp.lang.c

    Michael S <already5chosen@yahoo.com> writes:
    On Tue, 6 Jan 2026 10:31:41 -0500
    James Kuyper <jameskuyper@alumni.caltech.edu> wrote:


    If you know that an expression has one of the standard-named types or
    typedefs for with there is a corresponding printf() specifier, you
    should use that specifier. Otherwise, if you know that an expression
    has one of the types declared in <stdint.h>, you should use the
    corresponding macro #defined in <inttypes.h> to print it.

    I should? Really?
    Sorry, James, but you have no authority to make such statements.

    James is paraphrasing the C standard.

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

    Michael S <already5chosen@yahoo.com> writes:
    On Tue, 6 Jan 2026 10:31:41 -0500
    James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
    On 2026-01-06 04:29, Michael S wrote:
    On Tue, 6 Jan 2026 00:27:04 -0000 (UTC)
    Lawrence D’Oliveiro <ldo@nz.invalid> wrote:
    ...
    Section 7.8 of the C spec defines macros you can use so you don’t
    have to hard-code assumptions about the lengths of integers in
    printf-format strings.

    Did you ever try to use them? They look ugly.

    Which is more important, correctness or beauty?

    It depends.

    When I know for sure that incorrectness has no consequences, like
    in case of using %u to print 'unsigned long' on target with 32-bit
    longs, or like using %llu to print 'unsigned long' on target with
    64-bit longs, then beauty wins. Easily.

    Seriously?

    An example:

    unsigned long n = 42;
    printf("%u\n", n); // incorrect
    printf("%lu\n", n); // correct

    Are you really saying that the second version is so much uglier
    than the first that you'd rather write incorrect code?

    If unsigned int and unsigned long happen to be the same size, both
    are likely to print "42". But what if your code is later compiled
    on a system with 32-bit unsigned int and 64-bit unsigned long?

    Even if I were certain the code would never be ported (and such
    certainty is often unjustified), I'd much rather use the correct
    code than waste time figuring out which incorrect code will happen
    to "work" on the current system, with the current version of the
    compiler and runtime library. Oh, and gcc and clang both warn
    about an incorrect format string.

    I agree that the macros in <stdint.h> are ugly, and I rarely
    use them. If I want to print an integer value whose type I don't
    know, I'll probably cast to a predefined type that I know to be
    wide enough and use the specifier for that type. Though now that
    I think about it, I'm more likely to do that in throwaway code;
    for production code, I'd be more likely to use the <stdint.h> macros.

    [...]
    --
    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 James Kuyper@jameskuyper@alumni.caltech.edu to comp.lang.c on Tue Jan 6 19:44:47 2026
    From Newsgroup: comp.lang.c

    On 2026-01-06 13:05, Michael S wrote:
    On Tue, 6 Jan 2026 10:31:41 -0500
    James Kuyper <jameskuyper@alumni.caltech.edu> wrote:

    On 2026-01-06 04:29, Michael S wrote:
    On Tue, 6 Jan 2026 00:27:04 -0000 (UTC)
    Lawrence D’Oliveiro <ldo@nz.invalid> wrote:
    ...
    Section 7.8 of the C spec defines macros you can use so you don’t
    have to hard-code assumptions about the lengths of integers in
    printf-format strings.

    Did you ever try to use them? They look ugly.

    Which is more important, correctness or beauty?


    It depends.

    When I know for sure that incorrectness has no consequences, like

    If it did in fact have no consequences, it wouldn't be incorrect. It's
    the bad consequences of using the wrong format specifier that are the
    reason you should be avoiding doing so. It has undefined behavior, but
    the most common consequence is relatively mild - it just prints out the
    wrong value. Only in unusual circumstance can it result in memory access problems, or other more serious consequences.

    in case of using %u to print 'unsigned long' on target with 32-bit
    longs, or like using %llu to print 'unsigned long' on target with
    64-bit longs, then beauty wins. Easily.

    You've got it backwards. "%u" is the correct specifier to use for
    unsigned long on all platforms, whether unsigned long is 32, 36, or even
    48 bits. It is NOT the right specifier to use for any of the standard typedefs, such as time_t or uint32_t, unless you are certain that it is
    a typedef for unsigned long by the particular implementation you're
    currently using. The reason that they are typedefs is precisely because
    they can be typedef'd to different types by different implementations.

    If you know that an expression has one of the standard-named types or
    typedefs for with there is a corresponding printf() specifier, you
    should use that specifier. Otherwise, if you know that an expression
    has one of the types declared in <stdint.h>, you should use the
    corresponding macro #defined in <inttypes.h> to print it.

    I should? Really?
    Sorry, James, but you have no authority to make such statements.

    It is not a command, it is advice. You're free to ignore it. If you make
    a habit of ignoring it, you're likely to someday find that your code malfunctions when porting to a new platform.
    --- 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:14:20 2026
    From Newsgroup: comp.lang.c

    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    [...]
    I agree that the macros in <stdint.h> are ugly, and I rarely
    use them. If I want to print an integer value whose type I don't
    know, I'll probably cast to a predefined type that I know to be
    wide enough and use the specifier for that type. Though now that
    I think about it, I'm more likely to do that in throwaway code;
    for production code, I'd be more likely to use the <stdint.h> macros.

    Correction: the macros are defined in <inttypes.h>, not <stdint.h>. (<inttypes.h> includes <stdint.h> and extends it.)
    --
    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 bart@bc@freeuk.com to comp.lang.c on Wed Jan 7 01:14:21 2026
    From Newsgroup: comp.lang.c

    On 07/01/2026 00:44, James Kuyper wrote:
    On 2026-01-06 13:05, Michael S wrote:

    in case of using %u to print 'unsigned long' on target with 32-bit
    longs, or like using %llu to print 'unsigned long' on target with
    64-bit longs, then beauty wins. Easily.

    You've got it backwards. "%u" is the correct specifier to use for
    unsigned long on all platforms, whether unsigned long is 32, 36, or even
    48 bits.

    So not "%lu"?


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael S@already5chosen@yahoo.com to comp.lang.c on Wed Jan 7 13:41:54 2026
    From Newsgroup: comp.lang.c

    On Wed, 7 Jan 2026 01:14:21 +0000
    bart <bc@freeuk.com> wrote:

    On 07/01/2026 00:44, James Kuyper wrote:
    On 2026-01-06 13:05, Michael S wrote:

    in case of using %u to print 'unsigned long' on target with 32-bit
    longs, or like using %llu to print 'unsigned long' on target with
    64-bit longs, then beauty wins. Easily.

    You've got it backwards. "%u" is the correct specifier to use for
    unsigned long on all platforms, whether unsigned long is 32, 36, or
    even 48 bits.

    So not "%lu"?



    gcc and clang maintainers certainly think so.

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael S@already5chosen@yahoo.com to comp.lang.c on Wed Jan 7 14:01:34 2026
    From Newsgroup: comp.lang.c

    On Tue, 06 Jan 2026 16:29:04 -0800
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
    Michael S <already5chosen@yahoo.com> writes:
    On Tue, 6 Jan 2026 10:31:41 -0500
    James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
    On 2026-01-06 04:29, Michael S wrote:
    On Tue, 6 Jan 2026 00:27:04 -0000 (UTC)
    Lawrence D’Oliveiro <ldo@nz.invalid> wrote:
    ...
    Section 7.8 of the C spec defines macros you can use so you
    don’t have to hard-code assumptions about the lengths of
    integers in printf-format strings.

    Did you ever try to use them? They look ugly.

    Which is more important, correctness or beauty?

    It depends.

    When I know for sure that incorrectness has no consequences, like
    in case of using %u to print 'unsigned long' on target with 32-bit
    longs, or like using %llu to print 'unsigned long' on target with
    64-bit longs, then beauty wins. Easily.

    Seriously?

    An example:

    unsigned long n = 42;
    printf("%u\n", n); // incorrect
    printf("%lu\n", n); // correct

    Are you really saying that the second version is so much uglier
    than the first that you'd rather write incorrect code?

    No, I don't think that it is much uglier. At worst, I think that
    correct version is tiny bit uglier. Not enough for beauty to win over "correctness", even when correctness is non-consequential.
    I hoped that you followed the sub-thread from the beginning and did not
    lost the context yet. Which is (everywhere except LIN64)
    uint32_t n = 42;
    printf("n = %u\n", n); // incorrect
    printf("n = " PRIu32 "\n", n); // correct
    or on LIN64
    uint64_t n = 42;
    printf("n = %llu\n", n); // incorrect
    printf("n = " PRIu64 "\n", n); // correct
    Here in my book beauty wins by landslide.
    Although really it is not beauty wins. It's ugliness loses.
    If unsigned int and unsigned long happen to be the same size, both
    are likely to print "42". But what if your code is later compiled
    on a system with 32-bit unsigned int and 64-bit unsigned long?

    Even if I were certain the code would never be ported (and such
    certainty is often unjustified), I'd much rather use the correct
    code than waste time figuring out which incorrect code will happen
    to "work" on the current system, with the current version of the
    compiler and runtime library. Oh, and gcc and clang both warn
    about an incorrect format string.

    I agree that the macros in <stdint.h> are ugly, and I rarely
    use them. If I want to print an integer value whose type I don't
    know, I'll probably cast to a predefined type that I know to be
    wide enough and use the specifier for that type. Though now that
    I think about it, I'm more likely to do that in throwaway code;
    for production code, I'd be more likely to use the <stdint.h> macros.

    [...]

    I am happy that in practice your position is not too different from my position. It's just that irresistible urge of you to defend "right"
    things in NG discussions that creates an appearance of disagreeing.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Wed Jan 7 04:28:05 2026
    From Newsgroup: comp.lang.c

    Michael S <already5chosen@yahoo.com> writes:
    On Tue, 06 Jan 2026 16:29:04 -0800
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
    Michael S <already5chosen@yahoo.com> writes:
    On Tue, 6 Jan 2026 10:31:41 -0500
    James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
    On 2026-01-06 04:29, Michael S wrote:
    On Tue, 6 Jan 2026 00:27:04 -0000 (UTC)
    Lawrence D’Oliveiro <ldo@nz.invalid> wrote:
    ...
    Section 7.8 of the C spec defines macros you can use so you
    don’t have to hard-code assumptions about the lengths of
    integers in printf-format strings.

    Did you ever try to use them? They look ugly.

    Which is more important, correctness or beauty?

    It depends.

    When I know for sure that incorrectness has no consequences, like
    in case of using %u to print 'unsigned long' on target with 32-bit
    longs, or like using %llu to print 'unsigned long' on target with
    64-bit longs, then beauty wins. Easily.

    Seriously?

    An example:

    unsigned long n = 42;
    printf("%u\n", n); // incorrect
    printf("%lu\n", n); // correct

    Are you really saying that the second version is so much uglier
    than the first that you'd rather write incorrect code?

    No, I don't think that it is much uglier. At worst, I think that
    correct version is tiny bit uglier. Not enough for beauty to win over "correctness", even when correctness is non-consequential.

    I hoped that you followed the sub-thread from the beginning and did not
    lost the context yet.

    The context to which I replied was you favoring beauty over
    correctness and using "%u" to print an unsigned long value as
    an example.

    I find it difficult to express how strongly I disagree.

    Which is (everywhere except LIN64)
    uint32_t n = 42;
    printf("n = %u\n", n); // incorrect
    printf("n = " PRIu32 "\n", n); // correct

    printf("n = %lu\n", (unsigned long)n); // also correct

    or on LIN64
    uint64_t n = 42;
    printf("n = %llu\n", n); // incorrect
    printf("n = " PRIu64 "\n", n); // correct

    printf("n = %llu\n", (unsigned long long)n); // also correct

    As far as I'm concerned, the incorrect versions aren't even worth
    considering.

    Here in my book beauty wins by landslide.
    Although really it is not beauty wins. It's ugliness loses.

    I don't think much of your book.

    [...]

    I am happy that in practice your position is not too different from my position. It's just that irresistible urge of you to defend "right"
    things in NG discussions that creates an appearance of disagreeing.

    I don't know how you reached the conclusion that our positions are
    "not too different". As far as I can tell, they are completely
    different. You would knowingly write incorrect code.

    Given two alternative pieces of code, I prefer the prettier one
    if both are correct (and that is of course a matter of taste).
    But if one is incorrect, it doesn't matter how pretty it is.
    --
    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 Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Wed Jan 7 05:02:39 2026
    From Newsgroup: comp.lang.c

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

    On 2026-01-05 03:17, Andrey Tarasevich wrote:

    On Sun 1/4/2026 11:19 PM, Kenny McCormack wrote:

    The question is: How can you reliably printf() a time_t value?
    What conversion spec should you use?

    You can't. As far as the language is concerned, `time_t` is intended
    to be an opaque type. It has to be a real type, ...

    In C99, it was only required to be an arithmetic type. I pointed out
    that this would permit it to be, for example, double _Imaginary. [...]

    It's hard to imagine how time_t being an imaginary type could
    provide the semantics described in the C standard for time_t.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Wed Jan 7 05:06:28 2026
    From Newsgroup: comp.lang.c

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

    Michael S <already5chosen@yahoo.com> writes:

    On Tue, 6 Jan 2026 10:31:41 -0500
    James Kuyper <jameskuyper@alumni.caltech.edu> wrote:

    If you know that an expression has one of the standard-named types or
    typedefs for with there is a corresponding printf() specifier, you
    should use that specifier. Otherwise, if you know that an expression
    has one of the types declared in <stdint.h>, you should use the
    corresponding macro #defined in <inttypes.h> to print it.

    I should? Really?
    Sorry, James, but you have no authority to make such statements.

    James is paraphrasing the C standard.

    Really? What passage in the C standard is being paraphrased?
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From scott@scott@slp53.sl.home (Scott Lurndal) to comp.lang.c on Wed Jan 7 15:45:10 2026
    From Newsgroup: comp.lang.c

    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    Michael S <already5chosen@yahoo.com> writes:
    On Tue, 6 Jan 2026 10:31:41 -0500
    James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
    On 2026-01-06 04:29, Michael S wrote:
    On Tue, 6 Jan 2026 00:27:04 -0000 (UTC)
    Lawrence D’Oliveiro <ldo@nz.invalid> wrote:
    ...
    Section 7.8 of the C spec defines macros you can use so you don’t
    have to hard-code assumptions about the lengths of integers in
    printf-format strings.

    Did you ever try to use them? They look ugly.

    Which is more important, correctness or beauty?

    It depends.

    When I know for sure that incorrectness has no consequences, like
    in case of using %u to print 'unsigned long' on target with 32-bit
    longs, or like using %llu to print 'unsigned long' on target with
    64-bit longs, then beauty wins. Easily.

    Seriously?

    An example:

    unsigned long n = 42;
    printf("%u\n", n); // incorrect
    printf("%lu\n", n); // correct

    Are you really saying that the second version is so much uglier
    than the first that you'd rather write incorrect code?

    I suspect he may have been referring to code that needs
    to build for both 32-bit and 64-bit targets. One might
    typedef 'uint64' to be unsigned long long on both targets
    and just use %llu for the format string. BTDT.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From bart@bc@freeuk.com to comp.lang.c on Wed Jan 7 15:54:06 2026
    From Newsgroup: comp.lang.c

    On 07/01/2026 11:41, Michael S wrote:
    On Wed, 7 Jan 2026 01:14:21 +0000
    bart <bc@freeuk.com> wrote:

    On 07/01/2026 00:44, James Kuyper wrote:
    On 2026-01-06 13:05, Michael S wrote:

    in case of using %u to print 'unsigned long' on target with 32-bit
    longs, or like using %llu to print 'unsigned long' on target with
    64-bit longs, then beauty wins. Easily.

    You've got it backwards. "%u" is the correct specifier to use for
    unsigned long on all platforms, whether unsigned long is 32, 36, or
    even 48 bits.

    So not "%lu"?



    gcc and clang maintainers certainly think so.


    They think it is correct or not correct? If I compile this:

    #include <stdio.h>

    int main() {
    unsigned long a=0;
    printf("%u", a);
    }

    then gcc complains (given suitable options):

    warning: format '%u' expects argument of type 'unsigned int', but
    argument 2 has type 'long unsigned int' [-Wformat=]

    The suggests it is not correct.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael S@already5chosen@yahoo.com to comp.lang.c on Wed Jan 7 18:17:16 2026
    From Newsgroup: comp.lang.c

    On Wed, 7 Jan 2026 15:54:06 +0000
    bart <bc@freeuk.com> wrote:

    On 07/01/2026 11:41, Michael S wrote:
    On Wed, 7 Jan 2026 01:14:21 +0000
    bart <bc@freeuk.com> wrote:

    On 07/01/2026 00:44, James Kuyper wrote:
    On 2026-01-06 13:05, Michael S wrote:

    in case of using %u to print 'unsigned long' on target with
    32-bit longs, or like using %llu to print 'unsigned long' on
    target with 64-bit longs, then beauty wins. Easily.

    You've got it backwards. "%u" is the correct specifier to use for
    unsigned long on all platforms, whether unsigned long is 32, 36,
    or even 48 bits.

    So not "%lu"?



    gcc and clang maintainers certainly think so.


    They think it is correct or not correct? If I compile this:

    #include <stdio.h>

    int main() {
    unsigned long a=0;
    printf("%u", a);
    }

    then gcc complains (given suitable options):

    warning: format '%u' expects argument of type 'unsigned int', but
    argument 2 has type 'long unsigned int' [-Wformat=]

    The suggests it is not correct.

    May be, I was not sufficiently clear, but my post was agreeing with
    your point.

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From James Russell Kuyper Jr.@jameskuyper@alumni.caltech.edu to comp.lang.c on Wed Jan 7 12:45:30 2026
    From Newsgroup: comp.lang.c

    On 2026-01-06 20:14, bart wrote:
    On 07/01/2026 00:44, James Kuyper wrote:
    On 2026-01-06 13:05, Michael S wrote:

    in case of using %u to print 'unsigned long' on target with 32-bit
    longs, or like using %llu to print 'unsigned long' on  target with
    64-bit longs, then beauty wins. Easily.

    You've got it backwards. "%u" is the correct specifier to use for
    unsigned long on all platforms, whether unsigned long is 32, 36, or even
    48 bits.

    So not "%lu"?

    I was so wrapped up in making my point about the differences between
    standard named types and the <stdint.h> typedefs that I failed to notice
    he was using the wrong specifier for unsigned long. You're right, it
    should be "%lu".

    On a different point, I used time_t as an example. It would have been
    better to use ptrdiff_t instead, since <inttypes.h> has a macro for that
    type, and doesn't have one for time_t.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From James Russell Kuyper Jr.@jameskuyper@alumni.caltech.edu to comp.lang.c on Wed Jan 7 12:58:38 2026
    From Newsgroup: comp.lang.c

    On 2026-01-07 08:02, Tim Rentsch wrote:
    James Kuyper <jameskuyper@alumni.caltech.edu> writes:

    On 2026-01-05 03:17, Andrey Tarasevich wrote:
    ...
    You can't. As far as the language is concerned, `time_t` is intended
    to be an opaque type. It has to be a real type, ...

    In C99, it was only required to be an arithmetic type. I pointed out
    that this would permit it to be, for example, double _Imaginary. [...]

    It's hard to imagine how time_t being an imaginary type could
    provide the semantics described in the C standard for time_t.

    You'll need to elaborate on that. time_t is an opaque type which could,
    on one implementation, have been long double. Another implementation
    could have stored the same value as the imaginary component of
    _Imaginary long double, and could work with that value the same way as
    the first one. It would be a perverse implementation, but I see no
    serious obstacles to creating such an implementation. A good optimizer
    might even generate exactly the same machine code from C source code for
    the two implementations.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From James Russell Kuyper Jr.@jameskuyper@alumni.caltech.edu to comp.lang.c on Wed Jan 7 13:08:33 2026
    From Newsgroup: comp.lang.c

    On 2026-01-07 08:06, Tim Rentsch wrote:
    scott@slp53.sl.home (Scott Lurndal) writes:

    Michael S <already5chosen@yahoo.com> writes:

    On Tue, 6 Jan 2026 10:31:41 -0500
    James Kuyper <jameskuyper@alumni.caltech.edu> wrote:

    If you know that an expression has one of the standard-named types or
    typedefs for with there is a corresponding printf() specifier, you
    should use that specifier. Otherwise, if you know that an expression
    has one of the types declared in <stdint.h>, you should use the
    corresponding macro #defined in <inttypes.h> to print it.

    I should? Really?
    Sorry, James, but you have no authority to make such statements.

    James is paraphrasing the C standard.

    Really? What passage in the C standard is being paraphrased?

    This is advice, not paraphrased text from the C standard. It's based
    upon several facts that are mentioned in the text of the standard:

    1. <stdint.h> has typedefs for a variety integer types. They are
    typedefs precisely because they can, in general, specify different types
    on different implementations of C.

    2. <inttypes.h> contains macros for printf and scanf type specifiers appropriate for use with those typedefs.

    3. Using an inappropriate type specifier in either the printf() or
    scanf() families has undefined behavior, something that I generally want
    to avoid.

    For me, throughout most of my career, a high degree of portability was
    always required of my delivered code. Therefore, the desirability of
    using the <inttypes.h> type specifier macros, despite their clumsiness, follows from those facts. For other people, who don't mind restricting
    the portability of their code to platforms where it doesn't make a
    difference, the simplicity of using a fixed type specifier is more
    important.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Lawrence =?iso-8859-13?q?D=FFOliveiro?=@ldo@nz.invalid to comp.lang.c on Wed Jan 7 20:31:37 2026
    From Newsgroup: comp.lang.c

    On Wed, 7 Jan 2026 12:45:30 -0500, James Russell Kuyper Jr. wrote:

    On a different point, I used time_t as an example. It would have
    been better to use ptrdiff_t instead, since <inttypes.h> has a macro
    for that type, and doesn't have one for time_t.

    This is why you have configure scripts, so they can figure out the
    right types to use for building on your platform.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Wed Jan 7 13:28:45 2026
    From Newsgroup: comp.lang.c

    scott@slp53.sl.home (Scott Lurndal) writes:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    Michael S <already5chosen@yahoo.com> writes:
    On Tue, 6 Jan 2026 10:31:41 -0500
    James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
    On 2026-01-06 04:29, Michael S wrote:
    On Tue, 6 Jan 2026 00:27:04 -0000 (UTC)
    Lawrence D’Oliveiro <ldo@nz.invalid> wrote:
    ...
    Section 7.8 of the C spec defines macros you can use so you don’t
    have to hard-code assumptions about the lengths of integers in
    printf-format strings.

    Did you ever try to use them? They look ugly.

    Which is more important, correctness or beauty?

    It depends.

    When I know for sure that incorrectness has no consequences, like
    in case of using %u to print 'unsigned long' on target with 32-bit
    longs, or like using %llu to print 'unsigned long' on target with
    64-bit longs, then beauty wins. Easily.

    Seriously?

    An example:

    unsigned long n = 42;
    printf("%u\n", n); // incorrect
    printf("%lu\n", n); // correct

    Are you really saying that the second version is so much uglier
    than the first that you'd rather write incorrect code?

    I suspect he may have been referring to code that needs
    to build for both 32-bit and 64-bit targets. One might
    typedef 'uint64' to be unsigned long long on both targets
    and just use %llu for the format string. BTDT.

    In the quoted paragraph above, Michael wrote about using %u to print
    unsigned long, not about using %u to print some type hidden behind
    a typedef. If he didn't mean that, he can say so.

    But even if he meant to talk about printing, say, uint64_t values,
    my point stands.

    I wouldn't define my own "uint64" type. I'd just use "uint64_t",
    defined in <stdint.h>. And I'd use one of several *correct* ways
    to print uint64_t values.

    Michael, if you'd care to clarify, given:

    unsigned long n = 42;
    printf("%u\n", n); // incorrect
    printf("%lu\n", n); // correct

    (and assuming that unsigned int and unsigned long are the same width on
    the current implementation), do you really prefer the version marked as "incorrect"?
    --
    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 Wed Jan 7 13:32:27 2026
    From Newsgroup: comp.lang.c

    Lawrence D’Oliveiro <ldo@nz.invalid> writes:
    On Wed, 7 Jan 2026 12:45:30 -0500, James Russell Kuyper Jr. wrote:
    On a different point, I used time_t as an example. It would have
    been better to use ptrdiff_t instead, since <inttypes.h> has a macro
    for that type, and doesn't have one for time_t.

    This is why you have configure scripts, so they can figure out the
    right types to use for building on your platform.

    I don't follow. ptrdiff_t is defined in <stddef.h>, and is the correct
    type for the result of subtracting two pointers. What relevant
    information would a configure script give you?
    --
    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 S@already5chosen@yahoo.com to comp.lang.c on Thu Jan 8 01:26:20 2026
    From Newsgroup: comp.lang.c

    On Wed, 07 Jan 2026 13:28:45 -0800
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
    scott@slp53.sl.home (Scott Lurndal) writes:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    Michael S <already5chosen@yahoo.com> writes:
    On Tue, 6 Jan 2026 10:31:41 -0500
    James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
    On 2026-01-06 04:29, Michael S wrote:
    On Tue, 6 Jan 2026 00:27:04 -0000 (UTC)
    Lawrence D’Oliveiro <ldo@nz.invalid> wrote:
    ...
    Section 7.8 of the C spec defines macros you can use so you
    don’t have to hard-code assumptions about the lengths of
    integers in printf-format strings.

    Did you ever try to use them? They look ugly.

    Which is more important, correctness or beauty?

    It depends.

    When I know for sure that incorrectness has no consequences, like
    in case of using %u to print 'unsigned long' on target with 32-bit
    longs, or like using %llu to print 'unsigned long' on target with
    64-bit longs, then beauty wins. Easily.

    Seriously?

    An example:

    unsigned long n = 42;
    printf("%u\n", n); // incorrect
    printf("%lu\n", n); // correct

    Are you really saying that the second version is so much uglier
    than the first that you'd rather write incorrect code?

    I suspect he may have been referring to code that needs
    to build for both 32-bit and 64-bit targets. One might
    typedef 'uint64' to be unsigned long long on both targets
    and just use %llu for the format string. BTDT.

    In the quoted paragraph above, Michael wrote about using %u to print
    unsigned long, not about using %u to print some type hidden behind
    a typedef. If he didn't mean that, he can say so.

    But even if he meant to talk about printing, say, uint64_t values,
    my point stands.

    I wouldn't define my own "uint64" type. I'd just use "uint64_t",
    defined in <stdint.h>. And I'd use one of several *correct* ways
    to print uint64_t values.

    Michael, if you'd care to clarify, given:

    unsigned long n = 42;
    printf("%u\n", n); // incorrect
    printf("%lu\n", n); // correct

    (and assuming that unsigned int and unsigned long are the same width
    on the current implementation), do you really prefer the version
    marked as "incorrect"?

    I hoped that I already clarified that point more than one time.
    Obviously, I hoped wrong.
    In the case I am talking about n declared as uint32_t.
    uint32_t is an alias of 'unsigned long' on 32-bit embedded targets, on
    32-bit Linux, on 32-bit Windows and on 64-bit Windows. It is
    alias of 'unsigned int' on 64-bit Linux.
    Sometimes I move code between targets by myself, sometimes, rarely,
    other people do it. I don't want to have different versions of the code
    and I don't want to use ugly standard specifiers. Between two pretty
    and working variants I prefer the shorter one. Partly because it is
    guaranteed to work correctly on all my targets, including LIN64, but
    more importantly (in practice, 64-bit Linux is a very rare target in my
    daily routine) just because it is shorter. And I don't care that it is
    formally "incorrect" on my more common targets. Or may be not
    "formally", but both gcc and clang think so.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael S@already5chosen@yahoo.com to comp.lang.c on Thu Jan 8 01:29:07 2026
    From Newsgroup: comp.lang.c

    On Wed, 07 Jan 2026 13:32:27 -0800
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
    Lawrence D’Oliveiro <ldo@nz.invalid> writes:
    On Wed, 7 Jan 2026 12:45:30 -0500, James Russell Kuyper Jr. wrote:
    On a different point, I used time_t as an example. It would have
    been better to use ptrdiff_t instead, since <inttypes.h> has a
    macro for that type, and doesn't have one for time_t.

    This is why you have configure scripts, so they can figure out the
    right types to use for building on your platform.

    I don't follow. ptrdiff_t is defined in <stddef.h>, and is the
    correct type for the result of subtracting two pointers. What
    relevant information would a configure script give you?

    configure scripts are a cornerstone of software development religion for
    quite a few people.
    I hate them (scripts, not people) with passion.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Wed Jan 7 16:00:19 2026
    From Newsgroup: comp.lang.c

    Michael S <already5chosen@yahoo.com> writes:
    On Wed, 07 Jan 2026 13:28:45 -0800
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
    scott@slp53.sl.home (Scott Lurndal) writes:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    Michael S <already5chosen@yahoo.com> writes:
    On Tue, 6 Jan 2026 10:31:41 -0500
    James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
    On 2026-01-06 04:29, Michael S wrote:
    On Tue, 6 Jan 2026 00:27:04 -0000 (UTC)
    Lawrence D’Oliveiro <ldo@nz.invalid> wrote:
    ...
    Section 7.8 of the C spec defines macros you can use so you
    don’t have to hard-code assumptions about the lengths of
    integers in printf-format strings.

    Did you ever try to use them? They look ugly.

    Which is more important, correctness or beauty?

    It depends.

    When I know for sure that incorrectness has no consequences, like
    in case of using %u to print 'unsigned long' on target with 32-bit
    longs, or like using %llu to print 'unsigned long' on target with
    64-bit longs, then beauty wins. Easily.

    Seriously?

    An example:

    unsigned long n = 42;
    printf("%u\n", n); // incorrect
    printf("%lu\n", n); // correct

    Are you really saying that the second version is so much uglier
    than the first that you'd rather write incorrect code?

    I suspect he may have been referring to code that needs
    to build for both 32-bit and 64-bit targets. One might
    typedef 'uint64' to be unsigned long long on both targets
    and just use %llu for the format string. BTDT.

    In the quoted paragraph above, Michael wrote about using %u to print
    unsigned long, not about using %u to print some type hidden behind
    a typedef. If he didn't mean that, he can say so.

    But even if he meant to talk about printing, say, uint64_t values,
    my point stands.

    I wouldn't define my own "uint64" type. I'd just use "uint64_t",
    defined in <stdint.h>. And I'd use one of several *correct* ways
    to print uint64_t values.

    Michael, if you'd care to clarify, given:

    unsigned long n = 42;
    printf("%u\n", n); // incorrect
    printf("%lu\n", n); // correct

    (and assuming that unsigned int and unsigned long are the same width
    on the current implementation), do you really prefer the version
    marked as "incorrect"?

    I hoped that I already clarified that point more than one time.
    Obviously, I hoped wrong.

    And you still haven't. I asked a specific question above. What is
    your answer? Would you use a "%u" format to print a value that's
    defined with type unsigned long? I inferred from what you wrote
    that your answer would be yes. If your answer is no, I'll gladly
    accept that. (And if so, what you wrote previously was unclear,
    but I'm not going to worry about that if you clarify what you meant)

    You've previously indicated that you find "%lu" uglier than "%u",
    and that that's relevant to which one you would use. Do you still
    think so?

    I would appreciate direct yes or no answers to both of those
    questions.

    In the case I am talking about n declared as uint32_t.
    uint32_t is an alias of 'unsigned long' on 32-bit embedded targets, on
    32-bit Linux, on 32-bit Windows and on 64-bit Windows. It is
    alias of 'unsigned int' on 64-bit Linux.
    Sometimes I move code between targets by myself, sometimes, rarely,
    other people do it. I don't want to have different versions of the code
    and I don't want to use ugly standard specifiers. Between two pretty
    and working variants I prefer the shorter one. Partly because it is guaranteed to work correctly on all my targets, including LIN64, but
    more importantly (in practice, 64-bit Linux is a very rare target in my
    daily routine) just because it is shorter. And I don't care that it is formally "incorrect" on my more common targets. Or may be not
    "formally", but both gcc and clang think so.

    So you'd write code that happens to work on some implementations rather
    than code that's correct on all implementations.

    You know that unsigned long is at least 32 bits wide, and therefore that converting a uint32_t value to unsigned long will not lose information,
    and therefore that

    uint32_t x = 42;
    printf("%lu\n", (unsigned long)x);

    will work correctly. You can do this without using the ugly
    <inttypes.h> macros. Why wouldn't you?

    Sure, you can write code that happens to work on the only implementation
    you care about, but in my opinion, aside from being dangerous, it's just
    too much work. I don't care whether uint32_t is defined as unsigned int
    or unsigned long on a particular implementation, and I don't have to care.
    --
    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 S@already5chosen@yahoo.com to comp.lang.c on Thu Jan 8 02:38:46 2026
    From Newsgroup: comp.lang.c

    On Wed, 07 Jan 2026 16:00:19 -0800
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
    Michael S <already5chosen@yahoo.com> writes:
    On Wed, 07 Jan 2026 13:28:45 -0800
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
    scott@slp53.sl.home (Scott Lurndal) writes:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    Michael S <already5chosen@yahoo.com> writes:
    On Tue, 6 Jan 2026 10:31:41 -0500
    James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
    On 2026-01-06 04:29, Michael S wrote:
    On Tue, 6 Jan 2026 00:27:04 -0000 (UTC)
    Lawrence D’Oliveiro <ldo@nz.invalid> wrote:
    ...
    Section 7.8 of the C spec defines macros you can use so you
    don’t have to hard-code assumptions about the lengths of
    integers in printf-format strings.

    Did you ever try to use them? They look ugly.

    Which is more important, correctness or beauty?

    It depends.

    When I know for sure that incorrectness has no consequences,
    like in case of using %u to print 'unsigned long' on target
    with 32-bit longs, or like using %llu to print 'unsigned long'
    on target with 64-bit longs, then beauty wins. Easily.

    Seriously?

    An example:

    unsigned long n = 42;
    printf("%u\n", n); // incorrect
    printf("%lu\n", n); // correct

    Are you really saying that the second version is so much uglier
    than the first that you'd rather write incorrect code?

    I suspect he may have been referring to code that needs
    to build for both 32-bit and 64-bit targets. One might
    typedef 'uint64' to be unsigned long long on both targets
    and just use %llu for the format string. BTDT.

    In the quoted paragraph above, Michael wrote about using %u to
    print unsigned long, not about using %u to print some type hidden
    behind a typedef. If he didn't mean that, he can say so.

    But even if he meant to talk about printing, say, uint64_t values,
    my point stands.

    I wouldn't define my own "uint64" type. I'd just use "uint64_t",
    defined in <stdint.h>. And I'd use one of several *correct* ways
    to print uint64_t values.

    Michael, if you'd care to clarify, given:

    unsigned long n = 42;
    printf("%u\n", n); // incorrect
    printf("%lu\n", n); // correct

    (and assuming that unsigned int and unsigned long are the same
    width on the current implementation), do you really prefer the
    version marked as "incorrect"?

    I hoped that I already clarified that point more than one time.
    Obviously, I hoped wrong.

    And you still haven't. I asked a specific question above. What is
    your answer? Would you use a "%u" format to print a value that's
    defined with type unsigned long? I inferred from what you wrote
    that your answer would be yes. If your answer is no, I'll gladly
    accept that. (And if so, what you wrote previously was unclear,
    but I'm not going to worry about that if you clarify what you meant)

    When n declared as 'unsigned long' derectly rather than via unint32_t
    alias than the answer is 'no'.
    You've previously indicated that you find "%lu" uglier than "%u",
    and that that's relevant to which one you would use. Do you still
    think so?

    I would appreciate direct yes or no answers to both of those
    questions.

    It depends on how n declared.
    When it declared as 'unsigned long' then "lu" is not uglier.
    When it is defined as uint32_t it is uglier, despite the fact that on
    absolute majority of the targets that I care about the latter is an
    alias of the former.
    In the case I am talking about n declared as uint32_t.
    uint32_t is an alias of 'unsigned long' on 32-bit embedded targets,
    on 32-bit Linux, on 32-bit Windows and on 64-bit Windows. It is
    alias of 'unsigned int' on 64-bit Linux.
    Sometimes I move code between targets by myself, sometimes, rarely,
    other people do it. I don't want to have different versions of the
    code and I don't want to use ugly standard specifiers. Between two
    pretty and working variants I prefer the shorter one. Partly
    because it is guaranteed to work correctly on all my targets,
    including LIN64, but more importantly (in practice, 64-bit Linux is
    a very rare target in my daily routine) just because it is shorter.
    And I don't care that it is formally "incorrect" on my more common
    targets. Or may be not "formally", but both gcc and clang think so.


    So you'd write code that happens to work on some implementations
    rather than code that's correct on all implementations.

    No, it is correct on all implementation. Idea that in C, as opposed to
    C++, two unsigned integer types of the same size are somehow
    different is, IMHO, an abomination. And that is one not especially
    common case in which I don't care about opinion of the Standard.
    You know that unsigned long is at least 32 bits wide, and therefore
    that converting a uint32_t value to unsigned long will not lose
    information, and therefore that

    uint32_t x = 42;
    printf("%lu\n", (unsigned long)x);

    will work correctly. You can do this without using the ugly
    <inttypes.h> macros. Why wouldn't you?

    If it was named 'ulong' I'd seriously consider such solution. But when
    the name of type is not just rather long, but consists of two words as
    well, I wouldn't do it.
    Sure, you can write code that happens to work on the only
    implementation you care about, but in my opinion, aside from being
    dangerous, it's just too much work. I don't care whether uint32_t is
    defined as unsigned int or unsigned long on a particular
    implementation, and I don't have to care.

    I also don't care. Since for more than decade* I didn't have target
    with 'int' shorter than 32 bits, I just use %u. It takes me zero
    thinking.
    BTW, I am always aware of exact sizes of the basic types of the target
    that I work on. I don't feel comfotable without such knowledge. That
    how my mind works. It has problems with too abstract abstractions.
    ------
    * - or may be a little less than decade, I don't remember in which year
    exactly I did last change to project that runs on TI C2000, which is
    32-bit CPU, BTW, but TI being TI still had size of int that they
    had chosen.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Wed Jan 7 17:36:34 2026
    From Newsgroup: comp.lang.c

    Michael S <already5chosen@yahoo.com> writes:
    On Wed, 07 Jan 2026 16:00:19 -0800
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
    Michael S <already5chosen@yahoo.com> writes:
    On Wed, 07 Jan 2026 13:28:45 -0800
    [...]
    Michael, if you'd care to clarify, given:

    unsigned long n = 42;
    printf("%u\n", n); // incorrect
    printf("%lu\n", n); // correct

    (and assuming that unsigned int and unsigned long are the same
    width on the current implementation), do you really prefer the
    version marked as "incorrect"?

    I hoped that I already clarified that point more than one time.
    Obviously, I hoped wrong.

    And you still haven't. I asked a specific question above. What is
    your answer? Would you use a "%u" format to print a value that's
    defined with type unsigned long? I inferred from what you wrote
    that your answer would be yes. If your answer is no, I'll gladly
    accept that. (And if so, what you wrote previously was unclear,
    but I'm not going to worry about that if you clarify what you meant)

    When n declared as 'unsigned long' derectly rather than via unint32_t
    alias than the answer is 'no'.

    Thank you for answering that.

    You've previously indicated that you find "%lu" uglier than "%u",
    and that that's relevant to which one you would use. Do you still
    think so?

    I would appreciate direct yes or no answers to both of those
    questions.

    It depends on how n declared.
    When it declared as 'unsigned long' then "lu" is not uglier.
    When it is defined as uint32_t it is uglier, despite the fact that on absolute majority of the targets that I care about the latter is an
    alias of the former.

    Let me see if I understand you correctly.

    uint32_t n = 42;
    printf("%u\n", n);
    printf("%lu\n", n);

    In this context, you find "%lu" uglier than "%u"?

    [SNIP]
    --
    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 S@already5chosen@yahoo.com to comp.lang.c on Thu Jan 8 11:01:44 2026
    From Newsgroup: comp.lang.c

    On Wed, 07 Jan 2026 17:36:34 -0800
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:


    Let me see if I understand you correctly.

    uint32_t n = 42;
    printf("%u\n", n);
    printf("%lu\n", n);

    In this context, you find "%lu" uglier than "%u"?


    Yes.


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Thu Jan 8 11:35:27 2026
    From Newsgroup: comp.lang.c

    On 08/01/2026 00:26, Michael S wrote:
    On Wed, 07 Jan 2026 13:28:45 -0800
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:

    scott@slp53.sl.home (Scott Lurndal) writes:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    Michael S <already5chosen@yahoo.com> writes:
    On Tue, 6 Jan 2026 10:31:41 -0500
    James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
    On 2026-01-06 04:29, Michael S wrote:
    On Tue, 6 Jan 2026 00:27:04 -0000 (UTC)
    Lawrence D’Oliveiro <ldo@nz.invalid> wrote:
    ...
    Section 7.8 of the C spec defines macros you can use so you
    don’t have to hard-code assumptions about the lengths of >>>>>>>> integers in printf-format strings.

    Did you ever try to use them? They look ugly.

    Which is more important, correctness or beauty?

    It depends.

    When I know for sure that incorrectness has no consequences, like
    in case of using %u to print 'unsigned long' on target with 32-bit
    longs, or like using %llu to print 'unsigned long' on target with
    64-bit longs, then beauty wins. Easily.

    Seriously?

    An example:

    unsigned long n = 42;
    printf("%u\n", n); // incorrect
    printf("%lu\n", n); // correct

    Are you really saying that the second version is so much uglier
    than the first that you'd rather write incorrect code?

    I suspect he may have been referring to code that needs
    to build for both 32-bit and 64-bit targets. One might
    typedef 'uint64' to be unsigned long long on both targets
    and just use %llu for the format string. BTDT.

    In the quoted paragraph above, Michael wrote about using %u to print
    unsigned long, not about using %u to print some type hidden behind
    a typedef. If he didn't mean that, he can say so.

    But even if he meant to talk about printing, say, uint64_t values,
    my point stands.

    I wouldn't define my own "uint64" type. I'd just use "uint64_t",
    defined in <stdint.h>. And I'd use one of several *correct* ways
    to print uint64_t values.

    Michael, if you'd care to clarify, given:

    unsigned long n = 42;
    printf("%u\n", n); // incorrect
    printf("%lu\n", n); // correct

    (and assuming that unsigned int and unsigned long are the same width
    on the current implementation), do you really prefer the version
    marked as "incorrect"?


    I hoped that I already clarified that point more than one time.
    Obviously, I hoped wrong.

    In the case I am talking about n declared as uint32_t.
    uint32_t is an alias of 'unsigned long' on 32-bit embedded targets, on
    32-bit Linux, on 32-bit Windows and on 64-bit Windows. It is
    alias of 'unsigned int' on 64-bit Linux.

    "uint32_t" is an alias of "unsigned long" in the common embedded ABI for 32-bit ARM, as used by gcc and clang. I haven't tested if it is the
    same on the five other 32-bit proprietary ARM compilers I know of (IAR,
    GHS, Metrowerks, ImageCraft, ARM/Keil), and there are no doubt many
    other ARM compilers I have not heard of. I think I have used perhaps
    seven other 32-bit embedded processor architectures at least
    occasionally, and can probably think of a maybe another ten that I have
    never used. And there are many more.

    I think it would be extraordinarily arrogant of me to claim I know that "uint32_t is unsigned long on 32-bit embedded targets". Do you have
    such a wide experience that /you/ can justify that claim?

    Indeed, a quick check using godbolt.org shows that on 32-bit ARM Linux, uint32_t is "unsigned int", and for other 32-bit targets there is a mixture.

    Sometimes I move code between targets by myself, sometimes, rarely,
    other people do it. I don't want to have different versions of the code
    and I don't want to use ugly standard specifiers. Between two pretty
    and working variants I prefer the shorter one. Partly because it is guaranteed to work correctly on all my targets, including LIN64, but
    more importantly (in practice, 64-bit Linux is a very rare target in my
    daily routine) just because it is shorter. And I don't care that it is formally "incorrect" on my more common targets. Or may be not
    "formally", but both gcc and clang think so.


    This seems like a fine example of cutting off your own nose to spite
    your face. The single worst feature (IMHO) of printf and friends is
    that you have to match up the format string with the parameters and
    their types, or you have UB. Thus most compilers and static checkers
    can check literal format strings and the compare them to the number and
    types of the parameters. Warnings like gcc's "-Wformat" turn one of C's biggest static checking holes into almost a non-issue for many cases.
    And you throw that out just because you think "%lu" is uglier than "%u".

    I've been working with embedded C development for 30+ years. And I find
    that embedded C developers fall into two categories - those that use
    compiler warnings as obsessively as practically possible, and those that
    write crappy code with glaring errors. That division is almost
    independent of experience - people who have been in the branch for
    decades make mistakes too.


    I don't think you will find anyone who will tell you "PRIu32" and
    friends are nice and neat, and look good in a string literal. And if
    you are writing code that does not have to be portable (most code does
    not), I see nothing wrong with using "%lu" for uint32_t if you know your platform makes it an unsigned long int. Or you can, as others have
    suggested, use "%u" and cast your uint32_t to "unsigned" (or "%lu" and "unsigned long", or whatever makes sense to you). If you have a modern
    enough C library, you can use "%w32u" for uint32_t. Or you can use your
    own specific functions (very common in the embedded world - "printf" has
    a /lot/ overhead), or variadic and _Generic macros to get type-safe
    printing. There are many possibilities of doing this right (where
    "right" depends on your balances between convenience and portability) -
    there is no justification that I can see for doing it wrong.


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Thu Jan 8 17:13:21 2026
    From Newsgroup: comp.lang.c

    On 07/01/2026 02:14, bart wrote:
    On 07/01/2026 00:44, James Kuyper wrote:
    On 2026-01-06 13:05, Michael S wrote:

    in case of using %u to print 'unsigned long' on target with 32-bit
    longs, or like using %llu to print 'unsigned long' on  target with
    64-bit longs, then beauty wins. Easily.

    You've got it backwards. "%u" is the correct specifier to use for
    unsigned long on all platforms, whether unsigned long is 32, 36, or even
    48 bits.

    So not "%lu"?



    "%lu" is, as you point out, the correct specifier for "unsigned long".

    James made a mistake here - it's unusual for him, but we are all
    fallible. That is why it is a good idea to enable whatever static
    warnings you can get from a compiler, as long as they don't conflict
    with your particular coding style. And that is why it is madness for
    Michael to disable something as useful as printf format checking just
    because he thinks "%lu" is "ugly".

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Thu Jan 8 09:54:34 2026
    From Newsgroup: comp.lang.c

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

    On 2026-01-07 08:06, Tim Rentsch wrote:

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

    Michael S <already5chosen@yahoo.com> writes:

    On Tue, 6 Jan 2026 10:31:41 -0500
    James Kuyper <jameskuyper@alumni.caltech.edu> wrote:

    If you know that an expression has one of the standard-named types or >>>>> typedefs for with there is a corresponding printf() specifier, you
    should use that specifier. Otherwise, if you know that an expression >>>>> has one of the types declared in <stdint.h>, you should use the
    corresponding macro #defined in <inttypes.h> to print it.

    I should? Really?
    Sorry, James, but you have no authority to make such statements.

    James is paraphrasing the C standard.

    Really? What passage in the C standard is being paraphrased?

    This is advice, not paraphrased text from the C standard. [...]

    I was responding to Scotty Lurndal's statement that the C
    standard was being paraphrased (by someone, it didn't matter to
    me who). I don't care about whether his statement is true; my
    interest is only in what part of the C standard he thinks is
    being paraphrased. He is in a position to answer that question,
    and more to the point he is the only person who is.

    Unrelated matter: a couple of your recent postings show a name
    change to a longer form of your name. I don't know what might
    have prompted that change, but for what it's worth I like the
    earlier shorter form better, if only for consistency with
    earlier postings.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Thu Jan 8 10:08:24 2026
    From Newsgroup: comp.lang.c

    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:

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

    On 2026-01-07 08:06, Tim Rentsch wrote:

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

    I was responding to Scotty Lurndal's statement that the C

    Sorry, Scott Lurndal. My bad.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From scott@scott@slp53.sl.home (Scott Lurndal) to comp.lang.c on Thu Jan 8 18:36:42 2026
    From Newsgroup: comp.lang.c

    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> writes:

    On 2026-01-07 08:06, Tim Rentsch wrote:

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

    Michael S <already5chosen@yahoo.com> writes:

    On Tue, 6 Jan 2026 10:31:41 -0500
    James Kuyper <jameskuyper@alumni.caltech.edu> wrote:

    If you know that an expression has one of the standard-named types or >>>>>> typedefs for with there is a corresponding printf() specifier, you >>>>>> should use that specifier. Otherwise, if you know that an expression >>>>>> has one of the types declared in <stdint.h>, you should use the
    corresponding macro #defined in <inttypes.h> to print it.

    I should? Really?
    Sorry, James, but you have no authority to make such statements.

    James is paraphrasing the C standard.

    Really? What passage in the C standard is being paraphrased?

    This is advice, not paraphrased text from the C standard. [...]

    I was responding to Scotty Lurndal's statement that the C
    standard was being paraphrased (by someone, it didn't matter to
    me who). I don't care about whether his statement is true; my
    interest is only in what part of the C standard he thinks is
    being paraphrased. He is in a position to answer that question,
    and more to the point he is the only person who is.

    It's pretty clear that the standard describes the printf
    function and the methods used to match the format characters
    to the data types of the arguments. The fact that James
    framed that as advice doesn't change interpretation of
    the text of the standard, whether or not you consider
    that to be a paraphrase.


    "The main rules for paraphrasing are to fully understand
    the original text, restate its core idea in your own words
    and sentence structure, use synonyms, and always cite the
    original source to avoid plagiarism, even if the wording is different.

    And it is spelled "Scott".

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Thu Jan 8 13:05:23 2026
    From Newsgroup: comp.lang.c

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

    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:

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

    On 2026-01-07 08:06, Tim Rentsch wrote:

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

    Michael S <already5chosen@yahoo.com> writes:

    On Tue, 6 Jan 2026 10:31:41 -0500
    James Kuyper <jameskuyper@alumni.caltech.edu> wrote:

    If you know that an expression has one of the standard-named
    types or typedefs for with there is a corresponding printf()
    specifier, you should use that specifier. Otherwise, if you
    know that an expression has one of the types declared in
    <stdint.h>, you should use the corresponding macro #defined in
    <inttypes.h> to print it.

    I should? Really?
    Sorry, James, but you have no authority to make such statements.

    James is paraphrasing the C standard.

    Really? What passage in the C standard is being paraphrased?

    This is advice, not paraphrased text from the C standard. [...]

    I was responding to Scotty Lurndal's statement that the C
    standard was being paraphrased (by someone, it didn't matter to
    me who). I don't care about whether his statement is true; my
    interest is only in what part of the C standard he thinks is
    being paraphrased. He is in a position to answer that question,
    and more to the point he is the only person who is.

    It's pretty clear that the standard describes the printf
    function and the methods used to match the format characters
    to the data types of the arguments. The fact that James
    framed that as advice doesn't change interpretation of
    the text of the standard, whether or not you consider
    that to be a paraphrase.


    "The main rules for paraphrasing are to fully understand the
    original text, restate its core idea in your own words and
    sentence structure, use synonyms, and always cite the original
    source to avoid plagiarism, even if the wording is different.

    I see where the C standard says the macros in inttypes.h are
    suitable for use with printf (and scanf). That isn't at all
    the same as saying people should use them. Just because
    something can be done doesn't mean it should be done. James's
    statement "you should use the corresponding macro" is not a
    paraphrase, it's a statement of his opinion.

    And it is spelled "Scott".

    Yes, that was an inadvertent typo, and I had already posted
    a correction.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From James Russell Kuyper Jr.@jameskuyper@alumni.caltech.edu to comp.lang.c on Thu Jan 8 19:16:19 2026
    From Newsgroup: comp.lang.c

    On 2026-01-07 16:32, Keith Thompson wrote:
    Lawrence D’Oliveiro <ldo@nz.invalid> writes:
    On Wed, 7 Jan 2026 12:45:30 -0500, James Russell Kuyper Jr. wrote:
    On a different point, I used time_t as an example. It would have
    been better to use ptrdiff_t instead, since <inttypes.h> has a macro
    for that type, and doesn't have one for time_t.

    This is why you have configure scripts, so they can figure out the
    right types to use for building on your platform.

    I don't follow. ptrdiff_t is defined in <stddef.h>, and is the correct
    type for the result of subtracting two pointers. What relevant
    information would a configure script give you?

    I suspect he's referring to time_t, not ptrdiff_t. I mentioned the
    absence of a macro #defined in <inttypes.h> as a reason why I should not
    have used time_t as an example.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From James Russell Kuyper Jr.@jameskuyper@alumni.caltech.edu to comp.lang.c on Thu Jan 8 19:31:13 2026
    From Newsgroup: comp.lang.c

    On 2026-01-07 19:38, Michael S wrote:
    On Wed, 07 Jan 2026 16:00:19 -0800
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
    ...
    So you'd write code that happens to work on some implementations
    rather than code that's correct on all implementations.


    No, it is correct on all implementation. Idea that in C, as opposed to
    C++, two unsigned integer types of the same size are somehow
    different is, IMHO, an abomination. And that is one not especially
    common case in which I don't care about opinion of the Standard.

    We're not talking about two unsigned integer types with same size. We're talking about unsigned long, which can be any size >= 32 bits, and
    uint32_t, which can only be exactly 32 bits. Your code is NOT portable
    to a platform where unsigned long is greater than 32 bits.

    ...
    I also don't care. Since for more than decade* I didn't have target
    with 'int' shorter than 32 bits, I just use %u. It takes me zero
    thinking.

    As a general rule, I find that people who claim a decision requires no
    thought generally are referring to a decision that should have been made differently if sufficient thought had been put into it. This is a prime example.

    BTW, I am always aware of exact sizes of the basic types of the target
    that I work on. I don't feel comfotable without such knowledge. That
    how my mind works. It has problems with too abstract abstractions.

    I'd have no problem with your approach if you hadn't falsely claimed
    that "It is correct on all platforms". There's nothing wrong with code
    that is intentionally platform specific. Platform-specific code that the author incorrectly believes to be "correct on all platforms" is a problem.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Fri Jan 9 09:25:16 2026
    From Newsgroup: comp.lang.c

    On 08/01/2026 01:38, Michael S wrote:

    No, it is correct on all implementation. Idea that in C, as opposed to
    C++, two unsigned integer types of the same size are somehow
    different is, IMHO, an abomination. And that is one not especially
    common case in which I don't care about opinion of the Standard.

    You can have the opinion that any two integer types of the same size
    /should/ be fully interchangeable in C. That's a reasonable opinion,
    and you are not the only one to think that way.

    But the C language is defined differently. There are a number of
    situations where different integer types have the same size (and range
    and representation), yet are different types. On most 32-bit platforms, "long" is the same size as either "int" or "long long". But it is not type-compatible with either. "uint32_t" will probably be an alias for
    either "unsigned int" or "unsigned long" (but could on some platforms be
    an alias for "unsigned char", "char", "unsigned short", or an extended
    integer type).

    It does not really matter if you think the C language works the way you
    would like it to - when you program in C, the C standard is the contract between you and the compiler. We all have aspects of C that we dislike,
    and I am sure the same applies to compiler writers, but we all agree to
    stick to a common definition of the language. If you try to code in
    some C-like language that works the way /you/ would like it to, you will
    run into trouble when the compiler interprets your code differently from
    how you had intended.

    Use the types as the standard specifies. If the standard says "use %lu
    for this type, and %u for that type", then do that. If the standard
    says "pointers to unsigned int are incompatible with pointers to
    unsigned long, even if the integer types are the same size", then don't
    mix such pointer types. It's not that hard. Yes, occasionally it can
    be a little inconvenient or "ugly", but writing correct code pays off.

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael S@already5chosen@yahoo.com to comp.lang.c on Fri Jan 9 14:18:59 2026
    From Newsgroup: comp.lang.c

    On Thu, 8 Jan 2026 19:31:13 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:

    On 2026-01-07 19:38, Michael S wrote:
    On Wed, 07 Jan 2026 16:00:19 -0800
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
    ...
    So you'd write code that happens to work on some implementations
    rather than code that's correct on all implementations.


    No, it is correct on all implementation. Idea that in C, as opposed
    to C++, two unsigned integer types of the same size are somehow
    different is, IMHO, an abomination. And that is one not especially
    common case in which I don't care about opinion of the Standard.

    We're not talking about two unsigned integer types with same size.
    We're talking about unsigned long, which can be any size >= 32 bits,
    and uint32_t, which can only be exactly 32 bits. Your code is NOT
    portable to a platform where unsigned long is greater than 32 bits.


    I don't know how you came to discussions of what is possible.
    My statement was concrete. It was about platforms like Windows (of all
    flavors) and 2-3 specific 32-bit embedded targets that I currently
    care about.
    On all these platforms uint32_t is alias of 'unsigned long' which is
    32-bit wide. 'unsigned int' is also 32-bit wide.
    I claim that *on these platforms* uint32_t and 'unsigned int' are *not* different types. I don't care to what the Standard says about it.
    I do care about what gcc says about it because I am annoyed by warnings
    that I consider pointless.

    Printing uint32_t values on these platforms with %u specifier, apart
    from advantage of being shorter, has advantage of being undoubtedly
    correct on LIN64. Unlike printing with %lu.

    Now, going one step further and using more intimate knowledge of SysV
    ABI I can argue with myself and prove (could I? I am only 95% sure)
    that on LIN64 %lu will also always print correct result. But I don't
    want to take this step.

    ...
    I also don't care. Since for more than decade* I didn't have target
    with 'int' shorter than 32 bits, I just use %u. It takes me zero
    thinking.

    As a general rule, I find that people who claim a decision requires
    no thought generally are referring to a decision that should have
    been made differently if sufficient thought had been put into it.
    This is a prime example.

    BTW, I am always aware of exact sizes of the basic types of the
    target that I work on. I don't feel comfotable without such
    knowledge. That how my mind works. It has problems with too
    abstract abstractions.

    I'd have no problem with your approach if you hadn't falsely claimed
    that "It is correct on all platforms".

    Which I didn't.

    There's nothing wrong with
    code that is intentionally platform specific. Platform-specific code
    that the author incorrectly believes to be "correct on all platforms"
    is a problem.


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Fri Jan 9 13:49:51 2026
    From Newsgroup: comp.lang.c

    On 09/01/2026 13:18, Michael S wrote:
    On Thu, 8 Jan 2026 19:31:13 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:

    On 2026-01-07 19:38, Michael S wrote:
    On Wed, 07 Jan 2026 16:00:19 -0800
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
    ...
    So you'd write code that happens to work on some implementations
    rather than code that's correct on all implementations.


    No, it is correct on all implementation. Idea that in C, as opposed
    to C++, two unsigned integer types of the same size are somehow
    different is, IMHO, an abomination. And that is one not especially
    common case in which I don't care about opinion of the Standard.

    We're not talking about two unsigned integer types with same size.
    We're talking about unsigned long, which can be any size >= 32 bits,
    and uint32_t, which can only be exactly 32 bits. Your code is NOT
    portable to a platform where unsigned long is greater than 32 bits.


    I don't know how you came to discussions of what is possible.
    My statement was concrete. It was about platforms like Windows (of all flavors) and 2-3 specific 32-bit embedded targets that I currently
    care about.
    On all these platforms uint32_t is alias of 'unsigned long' which is
    32-bit wide. 'unsigned int' is also 32-bit wide.
    I claim that *on these platforms* uint32_t and 'unsigned int' are *not* different types. I don't care to what the Standard says about it.

    Of course they are different types. In C, "unsigned int" and "unsigned
    long" are different types. The standard says so - and it is the
    standard that defines the language.

    I do care about what gcc says about it because I am annoyed by warnings
    that I consider pointless.

    The warnings are not pointless, despite what you might think. And of
    course gcc is not going to modify its warnings to pander to someone who
    has their own personal ideas about what C should be. We /all/ have
    ideas about how C could be better for our own needs. But outside the
    realm of personal languages where the single user also designed the
    language and wrote the compiler, you have to work with the language as
    it is defined.

    For a clear example of the differences between unsigned int and unsigned
    long, look at the generated code here:

    <https://godbolt.org/z/hdjz6Y7vY>

    That is for embedded 32-bit ARM, where "uint32_t" is "unsigned long",
    and is the same size as "unsigned int". Then try swapping the compiler
    to the 32-bit ARM gcc Linux version - here "uint32_t" is "unsigned int",
    and again the same size as "unsigned long". Look at the differences in
    the code.

    It doesn't matter if /you/ think that all 32-bit integer types should be
    the same - in C, they are not. And therefore in C compilers, they are
    not the same.


    Printing uint32_t values on these platforms with %u specifier, apart
    from advantage of being shorter, has advantage of being undoubtedly
    correct on LIN64. Unlike printing with %lu.

    But printing uint32_t with "%u" on 32-bit EABI ARM is not correct - it
    is UB. It will /probably/ work, but maybe some day you will come across
    a situation where it will not.

    I have a lot of trouble understanding why you would go out of your way
    to knowingly write incorrect code - prioritising tiny, irrelevant
    savings in source code space over correct, guaranteed, portable code
    that can be automatically checked by tools.

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From wij@wyniijj5@gmail.com to comp.lang.c on Sat Jan 10 00:17:36 2026
    From Newsgroup: comp.lang.c

    On Fri, 2026-01-09 at 13:49 +0100, David Brown wrote:
    On 09/01/2026 13:18, Michael S wrote:
    On Thu, 8 Jan 2026 19:31:13 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:

    On 2026-01-07 19:38, Michael S wrote:
    On Wed, 07 Jan 2026 16:00:19 -0800
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
    ...
    So you'd write code that happens to work on some implementations rather than code that's correct on all implementations.
     

    No, it is correct on all implementation. Idea that in C, as opposed
    to C++, two unsigned integer types of the same size are somehow different is, IMHO, an abomination. And that is one not especially common case in which I don't care about opinion of the Standard.

    We're not talking about two unsigned integer types with same size.
    We're talking about unsigned long, which can be any size >= 32 bits,
    and uint32_t, which can only be exactly 32 bits. Your code is NOT portable to a platform where unsigned long is greater than 32 bits.


    I don't know how you came to discussions of what is possible.
    My statement was concrete. It was about platforms like Windows (of all flavors) and 2-3 specific 32-bit embedded targets that I currently
    care about.
    On all these platforms uint32_t is alias of 'unsigned long' which is
    32-bit wide. 'unsigned int' is also 32-bit wide.
    I claim that *on these platforms* uint32_t and 'unsigned int' are *not* different types. I don't care to what the Standard says about it.

    Of course they are different types.  In C, "unsigned int" and "unsigned long" are different types.  The standard says so - and it is the
    standard that defines the language.

    I do care about what gcc says about it because I am annoyed by warnings that I consider pointless.

    The warnings are not pointless, despite what you might think.  And of course gcc is not going to modify its warnings to pander to someone who
    has their own personal ideas about what C should be.  We /all/ have
    ideas about how C could be better for our own needs.  But outside the
    realm of personal languages where the single user also designed the
    language and wrote the compiler, you have to work with the language as
    it is defined.

    For a clear example of the differences between unsigned int and unsigned long, look at the generated code here:

    <https://godbolt.org/z/hdjz6Y7vY>

    That is for embedded 32-bit ARM, where "uint32_t" is "unsigned long",
    and is the same size as "unsigned int".  Then try swapping the compiler
    to the 32-bit ARM gcc Linux version - here "uint32_t" is "unsigned int",
    and again the same size as "unsigned long".  Look at the differences in
    the code.

    It doesn't matter if /you/ think that all 32-bit integer types should be
    the same - in C, they are not.  And therefore in C compilers, they are
    not the same.


    Printing uint32_t values on these platforms with %u specifier, apart
    from advantage of being shorter, has advantage of being undoubtedly
    correct on LIN64. Unlike printing with %lu.

    But printing uint32_t with "%u" on 32-bit EABI ARM is not correct - it
    is UB.  It will /probably/ work, but maybe some day you will come across
    a situation where it will not.

    I have a lot of trouble understanding why you would go out of your way
    to knowingly write incorrect code - prioritising tiny, irrelevant
    savings in source code space over correct, guaranteed, portable code
    that can be automatically checked by tools.
    Snipet from ClassGuidelines.txt
    ...
    wrd(or notation)
    This function converts the argument object (a type, class,..) to text
    which can be used to construct the 'same' (including verified by
    operator==) object.
    Note: This function is not mandatory. But in OO design, the text
    may include the description of interactions between external
    events and sub-classes. This would form the basic proof of
    correctness of concept.
    My reply might not be directly in the topic of current post. Just jumpped in
    to reply. It looks to me the format character MUST match the type passed to  printf, otherwise UB.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From scott@scott@slp53.sl.home (Scott Lurndal) to comp.lang.c on Fri Jan 9 16:26:50 2026
    From Newsgroup: comp.lang.c

    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
    scott@slp53.sl.home (Scott Lurndal) writes:

    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:


    I was responding to Scotty Lurndal's statement that the C
    standard was being paraphrased (by someone, it didn't matter to
    me who). I don't care about whether his statement is true; my
    interest is only in what part of the C standard he thinks is
    being paraphrased. He is in a position to answer that question,
    and more to the point he is the only person who is.

    It's pretty clear that the standard describes the printf
    function and the methods used to match the format characters
    to the data types of the arguments. The fact that James
    framed that as advice doesn't change interpretation of
    the text of the standard, whether or not you consider
    that to be a paraphrase.


    "The main rules for paraphrasing are to fully understand the
    original text, restate its core idea in your own words and
    sentence structure, use synonyms, and always cite the original
    source to avoid plagiarism, even if the wording is different.

    I see where the C standard says the macros in inttypes.h are
    suitable for use with printf (and scanf). That isn't at all
    the same as saying people should use them.

    Why on earth would the put them there if they didn't expect
    them to be used?

    Just because
    something can be done doesn't mean it should be done.

    Sigh.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Fri Jan 9 11:27:34 2026
    From Newsgroup: comp.lang.c

    wij <wyniijj5@gmail.com> writes:
    On Fri, 2026-01-09 at 13:49 +0100, David Brown wrote:
    [...]
    I have a lot of trouble understanding why you would go out of your way
    to knowingly write incorrect code - prioritising tiny, irrelevant
    savings in source code space over correct, guaranteed, portable code
    that can be automatically checked by tools.

    Snipet from ClassGuidelines.txt
    ...
    wrd(or notation)
    This function converts the argument object (a type, class,..) to text
    [SNIP]

    My reply might not be directly in the topic of current post. Just jumpped in to reply.

    Huh?? "ClassGuideline.txt" (which I managed to find on Sourceforge)
    is a set of guidelines for C++ programming, apparently something
    you wrote. As far as I can tell, it has nothing to do with the
    current discussion or with the topic of this newsgroup. Why did
    you mention it here?

    It looks to me the format character MUST match the type passed to  printf, otherwise UB.

    It doesn't just look to you that way. The C standard says
    so explicitly. C17 and earlier says "If any argument is not
    the correct type for the corresponding conversion specification,
    the behavior is undefined." C23 changed the wording, saying that
    fprintf shall behave as if it uses va_arg; the description of va_arg
    says the behavior is undefined if the wrong type is used.

    Note that the description of va_arg allows, for example, using
    unsigned int for an argument of type int or vice versa *if* the
    value is within the range of both. If it had been the intent to
    allow incompatible types that happen to have the same size and
    representation, the standard would have said so.
    --
    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 James Russell Kuyper Jr.@jameskuyper@alumni.caltech.edu to comp.lang.c on Sat Jan 10 22:02:03 2026
    From Newsgroup: comp.lang.c

    On 2026-01-09 07:18, Michael S wrote:
    On Thu, 8 Jan 2026 19:31:13 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
    ...
    I'd have no problem with your approach if you hadn't falsely claimed
    that "It is correct on all platforms".

    Which I didn't.

    On 2026-01-07 19:38, Michael S wrote:
    ...
    No, it is correct on all implementation.

    --- 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:40:46 2026
    From Newsgroup: comp.lang.c

    On 2026-01-08 13:36, Scott Lurndal wrote:
    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> writes:

    On 2026-01-07 08:06, Tim Rentsch wrote:

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

    Michael S <already5chosen@yahoo.com> writes:

    On Tue, 6 Jan 2026 10:31:41 -0500
    James Kuyper <jameskuyper@alumni.caltech.edu> wrote:

    If you know that an expression has one of the standard-named types or >>>>>>> typedefs for with there is a corresponding printf() specifier, you >>>>>>> should use that specifier. Otherwise, if you know that an expression >>>>>>> has one of the types declared in <stdint.h>, you should use the
    corresponding macro #defined in <inttypes.h> to print it.
    ...
    James is paraphrasing the C standard.
    ...
    It's pretty clear that the standard describes the printf
    function and the methods used to match the format characters
    to the data types of the arguments. The fact that James
    framed that as advice doesn't change interpretation of
    the text of the standard, whether or not you consider
    that to be a paraphrase.

    I was advising against the practice of finding out what type uint32_t is
    a typedef for on the implementation you're currently using, and using
    the corresponding format specifier rather than the appropriate
    <inttypes.h> macro. For any given implementation, those will both work
    equally well, and nothing the standard says favors one over the other.
    It is the fact that I have, for most of my career, been required to
    produce highly portable code, which makes me prefer the ugly macros over
    the simpler specifiers. That is not, in any sense, a paraphrase of
    anything said in the standard. It is a deduction from what the standard
    says, applied to my particular working environment.

    What I didn't notice when I made my original comment is that he wasn't
    even using the appropriate format specifier, which would have been %lu,
    but %u, simply because, on the platform he was using, they happened to
    work equally well.


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael S@already5chosen@yahoo.com to comp.lang.c on Sun Jan 11 13:20:15 2026
    From Newsgroup: comp.lang.c

    On Sat, 10 Jan 2026 22:02:03 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:

    On 2026-01-09 07:18, Michael S wrote:
    On Thu, 8 Jan 2026 19:31:13 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
    ...
    I'd have no problem with your approach if you hadn't falsely
    claimed that "It is correct on all platforms".

    Which I didn't.

    On 2026-01-07 19:38, Michael S wrote:
    ...
    No, it is correct on all implementation.


    The quote is taken out of context.
    The context was that on platforms that have properties (a) and (b) (see
    below) printing variables declared as uint32_t via %u is probably UB
    according to the Standard (I don't know for sure, however it is
    probable), but it can't cause troubles with production C compiler. Or
    with any C compiler that is made in intention of being used rather than
    crafted to prove theoretical points.
    Properties are:
    a) uint32_t aliased to 'unsigned long'
    b) 'unsigned int' is at least 32-bit wide.

    I never claimed that it is good idea on targets with 'unsigned int'
    that is narrower.

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Sun Jan 11 04:59:47 2026
    From Newsgroup: comp.lang.c

    Michael S <already5chosen@yahoo.com> writes:
    On Sat, 10 Jan 2026 22:02:03 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
    On 2026-01-09 07:18, Michael S wrote:
    On Thu, 8 Jan 2026 19:31:13 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
    ...
    I'd have no problem with your approach if you hadn't falsely
    claimed that "It is correct on all platforms".

    Which I didn't.

    On 2026-01-07 19:38, Michael S wrote:
    ...
    No, it is correct on all implementation.

    The quote is taken out of context.
    The context was that on platforms that have properties (a) and (b) (see below) printing variables declared as uint32_t via %u is probably UB according to the Standard (I don't know for sure, however it is
    probable),

    I'm sure. uint32_t is an alias for some predefined integer type.

    This:
    uint32_t n = 42;
    printf("%u\n", n);
    has undefined behavior *unless* uint32_t happens to an alias for
    unsigned int in the current implementation -- not just any 32-bit
    unsigned integer type, only unsigned int.

    If uint32_t is an alias for unsigned long (which implies that
    unsigned long is exactly 32 bits), then the call's behavior is
    undefined. (It might happen to "work".)

    If uint32_t and unsigned long have different sizes, it still might
    happen happen to "work", depending on calling conventions. Passing a
    32-bit argument and telling printf to expect a 64-bit value clearly has undefined behavior, but perhaps both happen to be passed in 64-bit
    registers, for example.

    but it can't cause troubles with production C compiler. Or
    with any C compiler that is made in intention of being used rather than crafted to prove theoretical points.
    Properties are:
    a) uint32_t aliased to 'unsigned long'

    Not guaranteed by the language (and not true on the implementations
    I use most often).

    b) 'unsigned int' is at least 32-bit wide.

    Not guaranteed by the language (though it happens to be guaranteed by
    POSIX).

    I never claimed that it is good idea on targets with 'unsigned int'
    that is narrower.

    I claim that it's not a good idea on any target.

    I find it *much* easier to write portable code than to spend time
    figuring out what non-portable code will happens to work on the
    platforms I happen to care about today.

    uint32_t n = 42;
    printf("%lu\n", (unsigned long)n);

    unsigned long is guaranteed by the language to be at least 32 bits.
    The conversion is guaranteed not to lose information. The format
    matches the type of the argument. And the code will work correctly
    on any conforming hosted implementation. (It might involve an
    unnecessary 32 to 64 bit conversion, but given the overhead of
    printf, that's unlikely to be a problem -- and if it is, I can use
    the appropriate macro from <inttypes.h>.)

    And to my eyes, using "%u" with a uint32_t argument is *ugly*.
    --
    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 S@already5chosen@yahoo.com to comp.lang.c on Sun Jan 11 15:32:01 2026
    From Newsgroup: comp.lang.c

    On Sun, 11 Jan 2026 04:59:47 -0800
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:
    On Sat, 10 Jan 2026 22:02:03 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
    On 2026-01-09 07:18, Michael S wrote:
    On Thu, 8 Jan 2026 19:31:13 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
    wrote:
    ...
    I'd have no problem with your approach if you hadn't falsely
    claimed that "It is correct on all platforms".

    Which I didn't.

    On 2026-01-07 19:38, Michael S wrote:
    ...
    No, it is correct on all implementation.

    The quote is taken out of context.
    The context was that on platforms that have properties (a) and (b)
    (see below) printing variables declared as uint32_t via %u is
    probably UB according to the Standard (I don't know for sure,
    however it is probable),

    I'm sure. uint32_t is an alias for some predefined integer type.

    This:
    uint32_t n = 42;
    printf("%u\n", n);
    has undefined behavior *unless* uint32_t happens to an alias for
    unsigned int in the current implementation -- not just any 32-bit
    unsigned integer type, only unsigned int.

    If uint32_t is an alias for unsigned long (which implies that
    unsigned long is exactly 32 bits), then the call's behavior is
    undefined. (It might happen to "work".)


    What exactly, assuming that conditions (a) and (b) fulfilled, should implementation do to prevent it from working?
    I mean short of completely crazy things that will make maintainer
    immediately fired?

    If uint32_t and unsigned long have different sizes, it still might
    happen happen to "work", depending on calling conventions. Passing a
    32-bit argument and telling printf to expect a 64-bit value clearly
    has undefined behavior, but perhaps both happen to be passed in 64-bit registers, for example.


    And that is sort of intimate knowledge of the ABI that I don't want to
    exploit, as already mentioned in my other post in this sub-thread.

    but it can't cause troubles with production C compiler.
    Or with any C compiler that is made in intention of being used
    rather than crafted to prove theoretical points.
    Properties are:
    a) uint32_t aliased to 'unsigned long'

    Not guaranteed by the language (and not true on the implementations
    I use most often).


    Did I ever say that it is guaranteed by the language or that it is
    universal in any other way?
    Normally you have much better reading comprehension than one that you demonstrate in this discussion. I'd guess that it's because I somehow
    caused you to become angry.

    b) 'unsigned int' is at least 32-bit wide.

    Not guaranteed by the language (though it happens to be guaranteed by
    POSIX).


    Did I ever say etc ...

    I never claimed that it is good idea on targets with 'unsigned int'
    that is narrower.

    I claim that it's not a good idea on any target.

    I find it *much* easier to write portable code than to spend time
    figuring out what non-portable code will happens to work on the
    platforms I happen to care about today.

    uint32_t n = 42;
    printf("%lu\n", (unsigned long)n);

    unsigned long is guaranteed by the language to be at least 32 bits.
    The conversion is guaranteed not to lose information. The format
    matches the type of the argument. And the code will work correctly
    on any conforming hosted implementation. (It might involve an
    unnecessary 32 to 64 bit conversion, but given the overhead of
    printf, that's unlikely to be a problem -- and if it is, I can use
    the appropriate macro from <inttypes.h>.)

    And to my eyes, using "%u" with a uint32_t argument is *ugly*.


    To be fair, it is not ideal.
    The solution that I would prefer would be universal adaption of
    Microsoft's size specifiers I32 and I64. They are not going to win
    beauty competition, but in practice they are a lot more convenient to
    use than standard macros and are equally good at carrying programmer's intentions.

    Microsoft has strong influence in committee, but was not able to push
    it into C11, where they successfully forced hands of other members on
    few much bigger and more controversial issues.
    I don't know what it means, May be, there is bold technical reason
    behind non-standardization of these size specifiers. Or may be there is
    no reason and Microsoft simply never tried.


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Sun Jan 11 16:34:28 2026
    From Newsgroup: comp.lang.c

    On 11/01/2026 14:32, Michael S wrote:
    On Sun, 11 Jan 2026 04:59:47 -0800
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:
    On Sat, 10 Jan 2026 22:02:03 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
    On 2026-01-09 07:18, Michael S wrote:
    On Thu, 8 Jan 2026 19:31:13 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
    wrote:
    ...
    I'd have no problem with your approach if you hadn't falsely
    claimed that "It is correct on all platforms".

    Which I didn't.

    On 2026-01-07 19:38, Michael S wrote:
    ...
    > No, it is correct on all implementation.

    The quote is taken out of context.
    The context was that on platforms that have properties (a) and (b)
    (see below) printing variables declared as uint32_t via %u is
    probably UB according to the Standard (I don't know for sure,
    however it is probable),

    I'm sure. uint32_t is an alias for some predefined integer type.

    This:
    uint32_t n = 42;
    printf("%u\n", n);
    has undefined behavior *unless* uint32_t happens to an alias for
    unsigned int in the current implementation -- not just any 32-bit
    unsigned integer type, only unsigned int.

    If uint32_t is an alias for unsigned long (which implies that
    unsigned long is exactly 32 bits), then the call's behavior is
    undefined. (It might happen to "work".)


    What exactly, assuming that conditions (a) and (b) fulfilled, should implementation do to prevent it from working?
    I mean short of completely crazy things that will make maintainer
    immediately fired?

    If an architecture has 32-bit "unsigned long", then "unsigned int" is necessarily also 32-bit (since "unsigned int" is always at least 32-bit,
    and "unsigned long" cannot be smaller than "unsigned int"). The very
    fact that you listed "unsigned int is at least 32-bit wide" as an
    assumption shows you are not well versed with the basics of C standards
    in this area.

    I agree that it is difficult to imagine an implementation where
    "uint32_t" is "unsigned long" and where the code example you gave would
    not work as expected. But there are countless cases where C programmers
    have thought "it doesn't matter if this is UB, the compiler can't
    generate code other than the way I think it should". Could some future implementation on some future architecture do something I don't expect
    with the code example here? I am not willing to bet against that
    possibility - certainly not for something as petty as skipping a single
    letter in the source code.

    But to my mind, the prime disadvantage of writing this incorrect code
    (besides portability, which is not always a big concern) is that you
    block important and useful automated checks. That's just bad
    development practice. Don't worry about compiler maintainers getting
    fired for doing crazy things - worry about C programmers getting fired
    for doing crazy things. (I wouldn't immediately fire someone for
    writing code like this, but I'd reject it in a code review, and I'd have serious concerns about a programmer who knowingly wrote UB without
    vastly better justification than you have.)


    If uint32_t and unsigned long have different sizes, it still might
    happen happen to "work", depending on calling conventions. Passing a
    32-bit argument and telling printf to expect a 64-bit value clearly
    has undefined behavior, but perhaps both happen to be passed in 64-bit
    registers, for example.


    And that is sort of intimate knowledge of the ABI that I don't want to exploit, as already mentioned in my other post in this sub-thread.

    Writing code that is UB but "works as intended" /does/ require an and
    rely upon an intimate knowledge of the ABI. You are saying this works precisely because you know things about the ABI you are using. Writing correct code will keep it portable and working regardless of the ABI.


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael S@already5chosen@yahoo.com to comp.lang.c on Sun Jan 11 18:19:45 2026
    From Newsgroup: comp.lang.c

    On Sun, 11 Jan 2026 16:34:28 +0100
    David Brown <david.brown@hesbynett.no> wrote:

    On 11/01/2026 14:32, Michael S wrote:
    On Sun, 11 Jan 2026 04:59:47 -0800
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:
    On Sat, 10 Jan 2026 22:02:03 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
    wrote:
    On 2026-01-09 07:18, Michael S wrote:
    On Thu, 8 Jan 2026 19:31:13 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
    wrote:
    ...
    I'd have no problem with your approach if you hadn't falsely
    claimed that "It is correct on all platforms".

    Which I didn't.

    On 2026-01-07 19:38, Michael S wrote:
    ...
    > No, it is correct on all implementation.

    The quote is taken out of context.
    The context was that on platforms that have properties (a) and (b)
    (see below) printing variables declared as uint32_t via %u is
    probably UB according to the Standard (I don't know for sure,
    however it is probable),

    I'm sure. uint32_t is an alias for some predefined integer type.

    This:
    uint32_t n = 42;
    printf("%u\n", n);
    has undefined behavior *unless* uint32_t happens to an alias for
    unsigned int in the current implementation -- not just any 32-bit
    unsigned integer type, only unsigned int.

    If uint32_t is an alias for unsigned long (which implies that
    unsigned long is exactly 32 bits), then the call's behavior is
    undefined. (It might happen to "work".)


    What exactly, assuming that conditions (a) and (b) fulfilled, should implementation do to prevent it from working?
    I mean short of completely crazy things that will make maintainer immediately fired?

    If an architecture has 32-bit "unsigned long", then "unsigned int" is necessarily also 32-bit (since "unsigned int" is always at least
    32-bit,

    I am pretty sure that it is wrong.
    C Standard does not require for 'unsigned int' to be above 16 bits.

    and "unsigned long" cannot be smaller than "unsigned int").
    The very fact that you listed "unsigned int is at least 32-bit wide"
    as an assumption shows you are not well versed with the basics of C
    standards in this area.


    I am not well versed in the Standard. But in this particular case you
    are the one who doesn't know it.


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Sun Jan 11 20:25:28 2026
    From Newsgroup: comp.lang.c

    On 11/01/2026 17:19, Michael S wrote:
    On Sun, 11 Jan 2026 16:34:28 +0100
    David Brown <david.brown@hesbynett.no> wrote:

    On 11/01/2026 14:32, Michael S wrote:
    On Sun, 11 Jan 2026 04:59:47 -0800
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:
    On Sat, 10 Jan 2026 22:02:03 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
    wrote:
    On 2026-01-09 07:18, Michael S wrote:
    On Thu, 8 Jan 2026 19:31:13 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
    wrote:
    ...
    I'd have no problem with your approach if you hadn't falsely
    claimed that "It is correct on all platforms".

    Which I didn't.

    On 2026-01-07 19:38, Michael S wrote:
    ...
    > No, it is correct on all implementation.

    The quote is taken out of context.
    The context was that on platforms that have properties (a) and (b)
    (see below) printing variables declared as uint32_t via %u is
    probably UB according to the Standard (I don't know for sure,
    however it is probable),

    I'm sure. uint32_t is an alias for some predefined integer type.

    This:
    uint32_t n = 42;
    printf("%u\n", n);
    has undefined behavior *unless* uint32_t happens to an alias for
    unsigned int in the current implementation -- not just any 32-bit
    unsigned integer type, only unsigned int.

    If uint32_t is an alias for unsigned long (which implies that
    unsigned long is exactly 32 bits), then the call's behavior is
    undefined. (It might happen to "work".)


    What exactly, assuming that conditions (a) and (b) fulfilled, should
    implementation do to prevent it from working?
    I mean short of completely crazy things that will make maintainer
    immediately fired?

    If an architecture has 32-bit "unsigned long", then "unsigned int" is
    necessarily also 32-bit (since "unsigned int" is always at least
    32-bit,

    I am pretty sure that it is wrong.
    C Standard does not require for 'unsigned int' to be above 16 bits.


    Sorry, I jumbled that. It is unsigned long that must be at least 32
    bits, so your second requirement is not redundant.

    and "unsigned long" cannot be smaller than "unsigned int").
    The very fact that you listed "unsigned int is at least 32-bit wide"
    as an assumption shows you are not well versed with the basics of C
    standards in this area.


    I am not well versed in the Standard. But in this particular case you
    are the one who doesn't know it.


    I know it, but I still got it wrong here :-(

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

    Michael S <already5chosen@yahoo.com> writes:

    On Sat, 10 Jan 2026 22:02:03 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:

    On 2026-01-09 07:18, Michael S wrote:

    On Thu, 8 Jan 2026 19:31:13 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:

    ...

    I'd have no problem with your approach if you hadn't falsely
    claimed that "It is correct on all platforms".

    Which I didn't.

    On 2026-01-07 19:38, Michael S wrote:
    ...

    No, it is correct on all implementation.


    The quote is taken out of context.
    The context was that on platforms that have properties (a) and (b) (see below) printing variables declared as uint32_t via %u is probably UB according to the Standard (I don't know for sure, however it is
    probable), but it can't cause troubles with production C compiler. Or
    with any C compiler that is made in intention of being used rather than crafted to prove theoretical points.
    Properties are:
    a) uint32_t aliased to 'unsigned long'
    b) 'unsigned int' is at least 32-bit wide.

    It seems unlikely that any implementation would make such a
    choice. Can you name one that does?
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Sun Jan 11 11:58:47 2026
    From Newsgroup: comp.lang.c

    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:

    Michael S <already5chosen@yahoo.com> writes:

    On Sat, 10 Jan 2026 22:02:03 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:

    On 2026-01-09 07:18, Michael S wrote:

    On Thu, 8 Jan 2026 19:31:13 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:

    ...

    I'd have no problem with your approach if you hadn't falsely
    claimed that "It is correct on all platforms".

    Which I didn't.

    On 2026-01-07 19:38, Michael S wrote:
    ...

    No, it is correct on all implementation.

    The quote is taken out of context.
    The context was that on platforms that have properties (a) and (b) (see
    below) printing variables declared as uint32_t via %u is probably UB
    according to the Standard (I don't know for sure, however it is
    probable),

    I'm sure. uint32_t is an alias for some predefined integer type.

    Very likely, but I don't think the C standard requires it. TTBOMU
    the C standard allows the possibility of an implementation where
    uint32_t is type distinct from any other nameable type, and yet
    the implementation could still be conforming.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael S@already5chosen@yahoo.com to comp.lang.c on Sun Jan 11 23:51:04 2026
    From Newsgroup: comp.lang.c

    On Sun, 11 Jan 2026 11:51:43 -0800
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:

    On Sat, 10 Jan 2026 22:02:03 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:

    On 2026-01-09 07:18, Michael S wrote:

    On Thu, 8 Jan 2026 19:31:13 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
    wrote:

    ...

    I'd have no problem with your approach if you hadn't falsely
    claimed that "It is correct on all platforms".

    Which I didn't.

    On 2026-01-07 19:38, Michael S wrote:
    ...

    No, it is correct on all implementation.


    The quote is taken out of context.
    The context was that on platforms that have properties (a) and (b)
    (see below) printing variables declared as uint32_t via %u is
    probably UB according to the Standard (I don't know for sure,
    however it is probable), but it can't cause troubles with
    production C compiler. Or with any C compiler that is made in
    intention of being used rather than crafted to prove theoretical
    points. Properties are:
    a) uint32_t aliased to 'unsigned long'
    b) 'unsigned int' is at least 32-bit wide.

    It seems unlikely that any implementation would make such a
    choice. Can you name one that does?

    Four out of four target for which I write C programs for living in this
    decade:
    - Altera Nios2 (nios2-elf-gcc)
    - Arm Cortex-M bare metal (arm-none-eabi-gcc)
    - Win32-i386, various compilers
    - Win64-Amd64,various compilers

    Well, if I would be pedantic, then in this decade I also wrote several
    programs for Arm32 Linux, where I don't know whether uint32_t is alias
    of 'unsigned int' or 'unsigned long', few programs for AMD64 Linux,
    where I know that uint32_t is an alias of 'unsigned long' and may be one program for ARM64 Linux that is the same as AMD64 Linux.
    But all those outliers together constitute a tiny fraction of the code
    that I wrote recently.









    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Sun Jan 11 15:23:35 2026
    From Newsgroup: comp.lang.c

    Michael S <already5chosen@yahoo.com> writes:
    On Sun, 11 Jan 2026 04:59:47 -0800
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
    Michael S <already5chosen@yahoo.com> writes:
    On Sat, 10 Jan 2026 22:02:03 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
    On 2026-01-09 07:18, Michael S wrote:
    On Thu, 8 Jan 2026 19:31:13 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
    wrote:
    ...
    I'd have no problem with your approach if you hadn't falsely
    claimed that "It is correct on all platforms".

    Which I didn't.

    On 2026-01-07 19:38, Michael S wrote:
    ...
    No, it is correct on all implementation.

    The quote is taken out of context.
    The context was that on platforms that have properties (a) and (b)
    (see below) printing variables declared as uint32_t via %u is
    probably UB according to the Standard (I don't know for sure,
    however it is probable),

    I'm sure. uint32_t is an alias for some predefined integer type.

    This:
    uint32_t n = 42;
    printf("%u\n", n);
    has undefined behavior *unless* uint32_t happens to an alias for
    unsigned int in the current implementation -- not just any 32-bit
    unsigned integer type, only unsigned int.

    If uint32_t is an alias for unsigned long (which implies that
    unsigned long is exactly 32 bits), then the call's behavior is
    undefined. (It might happen to "work".)

    What exactly, assuming that conditions (a) and (b) fulfilled, should implementation do to prevent it from working?
    I mean short of completely crazy things that will make maintainer
    immediately fired?

    Most likely nothing. The behavior is undefined, so the standard places
    no requirement on implementations either to make it work as some might
    expect or to make it fail in some way.

    Both gcc and clang will issue compile-time warnings for mismatched
    format strings. They do so only if the format string is a string
    literal (or perhaps in other cases where the format string is known at
    compile time).

    If uint32_t and unsigned long have different sizes, it still might
    happen happen to "work", depending on calling conventions. Passing a
    32-bit argument and telling printf to expect a 64-bit value clearly
    has undefined behavior, but perhaps both happen to be passed in 64-bit
    registers, for example.

    And that is sort of intimate knowledge of the ABI that I don't want to exploit, as already mentioned in my other post in this sub-thread.

    So you're unwilling to assume that passing a 32-bit argument while
    telling printf to expect a 64-bit argument. Good for you, seriously.

    But you're willing to assume that it's ok if the argument and format
    string specify different 32-bit types. I'm not.

    but it can't cause troubles with production C compiler.
    Or with any C compiler that is made in intention of being used
    rather than crafted to prove theoretical points.
    Properties are:
    a) uint32_t aliased to 'unsigned long'

    Not guaranteed by the language (and not true on the implementations
    I use most often).

    Did I ever say that it is guaranteed by the language or that it is
    universal in any other way?
    Normally you have much better reading comprehension than one that you demonstrate in this discussion. I'd guess that it's because I somehow
    caused you to become angry.

    Let's not make this personal. I don't want to get into an argument
    about reading comprehension, but I'll point out that I didn't say that
    you said that it's guaranteed by the language. Not everything I write
    in a followup is a refutation of what was written in the previous article. Sometimes I'm simply adding more information.

    [...]

    I never claimed that it is good idea on targets with 'unsigned int'
    that is narrower.

    I claim that it's not a good idea on any target.

    I find it *much* easier to write portable code than to spend time
    figuring out what non-portable code will happens to work on the
    platforms I happen to care about today.

    uint32_t n = 42;
    printf("%lu\n", (unsigned long)n);

    unsigned long is guaranteed by the language to be at least 32 bits.
    The conversion is guaranteed not to lose information. The format
    matches the type of the argument. And the code will work correctly
    on any conforming hosted implementation. (It might involve an
    unnecessary 32 to 64 bit conversion, but given the overhead of
    printf, that's unlikely to be a problem -- and if it is, I can use
    the appropriate macro from <inttypes.h>.)

    And to my eyes, using "%u" with a uint32_t argument is *ugly*.

    To be fair, it is not ideal.
    The solution that I would prefer would be universal adaption of
    Microsoft's size specifiers I32 and I64. They are not going to win
    beauty competition, but in practice they are a lot more convenient to
    use than standard macros and are equally good at carrying programmer's intentions.

    The relative beauty of a feature that isn't available hardly seems
    relevant.

    The ideal solution is to write correct, and preferably portable,
    code in the first place. There are often good reasons to write
    non-portable code, but I suggest that fiding the correct format
    string to be ugly is not one of them.

    (Microsoft's documentation says that "I32" prefix applies to an
    argument of type __int32 or unsigned __int32. I don't know whether
    __int32 is compatible with int, with long, or neither, and I don't
    much care. I don't know what Microsoft guarantees about printf
    with incompatible types that happen to have the same size.)

    Microsoft has strong influence in committee, but was not able to push
    it into C11, where they successfully forced hands of other members on
    few much bigger and more controversial issues.
    I don't know what it means, May be, there is bold technical reason
    behind non-standardization of these size specifiers. Or may be there is
    no reason and Microsoft simply never tried.

    If I write code that depends on assumptions about the current
    platform, it still might not work for some possibly unrelated reason.
    If I write portable code in the first place and something goes
    wrong, I have one less thing to worry about as a possible cause of
    the error.
    --
    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 Sun Jan 11 14:56:17 2026
    From Newsgroup: comp.lang.c

    Michael S <already5chosen@yahoo.com> writes:
    On Sun, 11 Jan 2026 04:59:47 -0800
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
    Michael S <already5chosen@yahoo.com> writes:
    On Sat, 10 Jan 2026 22:02:03 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
    On 2026-01-09 07:18, Michael S wrote:
    On Thu, 8 Jan 2026 19:31:13 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
    wrote:
    ...
    I'd have no problem with your approach if you hadn't falsely
    claimed that "It is correct on all platforms".

    Which I didn't.

    On 2026-01-07 19:38, Michael S wrote:
    ...
    No, it is correct on all implementation.

    The quote is taken out of context.
    The context was that on platforms that have properties (a) and (b)
    (see below) printing variables declared as uint32_t via %u is
    probably UB according to the Standard (I don't know for sure,
    however it is probable),

    I'm sure. uint32_t is an alias for some predefined integer type.

    This:
    uint32_t n = 42;
    printf("%u\n", n);
    has undefined behavior *unless* uint32_t happens to an alias for
    unsigned int in the current implementation -- not just any 32-bit
    unsigned integer type, only unsigned int.

    If uint32_t is an alias for unsigned long (which implies that
    unsigned long is exactly 32 bits), then the call's behavior is
    undefined. (It might happen to "work".)

    What exactly, assuming that conditions (a) and (b) fulfilled, should implementation do to prevent it from working?
    I mean short of completely crazy things that will make maintainer
    immediately fired?

    Most likely nothing. The behavior is undefined, so the standard places
    no requirement on implementations either to make it work as some might
    expect or to make it fail in some way.

    Both gcc and clang will issue compile-time warnings for mismatched
    format strings. They do so only if the format string is a string
    literal (or perhaps in other cases where the format string is known at
    compile time).

    If uint32_t and unsigned long have different sizes, it still might
    happen happen to "work", depending on calling conventions. Passing a
    32-bit argument and telling printf to expect a 64-bit value clearly
    has undefined behavior, but perhaps both happen to be passed in 64-bit
    registers, for example.

    And that is sort of intimate knowledge of the ABI that I don't want to exploit, as already mentioned in my other post in this sub-thread.

    So you're unwilling to assume that passing a 32-bit argument while
    telling printf to expect a 64-bit argument. Good for you, seriously.

    But you're willing to assume that it's ok if the argument and format
    string specify different 32-bit types. I'm not.

    but it can't cause troubles with production C compiler.
    Or with any C compiler that is made in intention of being used
    rather than crafted to prove theoretical points.
    Properties are:
    a) uint32_t aliased to 'unsigned long'

    Not guaranteed by the language (and not true on the implementations
    I use most often).

    Did I ever say that it is guaranteed by the language or that it is
    universal in any other way?
    Normally you have much better reading comprehension than one that you demonstrate in this discussion. I'd guess that it's because I somehow
    caused you to become angry.

    Let's not make this personal. I don't want to get into an argument
    about reading comprehension, but I'll point out that I didn't say that
    you said that it's guaranteed by the language. Not everything I write
    in a followup is a refutation of what was written in the previous article. Sometimes I'm simply adding more information.

    [...]

    I never claimed that it is good idea on targets with 'unsigned int'
    that is narrower.

    I claim that it's not a good idea on any target.

    I find it *much* easier to write portable code than to spend time
    figuring out what non-portable code will happens to work on the
    platforms I happen to care about today.

    uint32_t n = 42;
    printf("%lu\n", (unsigned long)n);

    unsigned long is guaranteed by the language to be at least 32 bits.
    The conversion is guaranteed not to lose information. The format
    matches the type of the argument. And the code will work correctly
    on any conforming hosted implementation. (It might involve an
    unnecessary 32 to 64 bit conversion, but given the overhead of
    printf, that's unlikely to be a problem -- and if it is, I can use
    the appropriate macro from <inttypes.h>.)

    And to my eyes, using "%u" with a uint32_t argument is *ugly*.

    To be fair, it is not ideal.
    The solution that I would prefer would be universal adaption of
    Microsoft's size specifiers I32 and I64. They are not going to win
    beauty competition, but in practice they are a lot more convenient to
    use than standard macros and are equally good at carrying programmer's intentions.

    The relative beauty of a feature that isn't available hardly seems
    relevant.

    The ideal solution is to write correct, and preferably portable,
    code in the first place. There are often good reasons to write
    non-portable code, but I suggest that fiding the correct format
    string to be ugly is not one of them.

    (Microsoft's documentation says that "I32" prefix applies to an
    argument of type __int32 or unsigned __int32. I don't know whether
    __int32 is compatible with int, with long, or neither, and I don't
    much care. I don't know what Microsoft guarantees about printf
    with incompatible types that happen to have the same size.)

    Microsoft has strong influence in committee, but was not able to push
    it into C11, where they successfully forced hands of other members on
    few much bigger and more controversial issues.
    I don't know what it means, May be, there is bold technical reason
    behind non-standardization of these size specifiers. Or may be there is
    no reason and Microsoft simply never tried.

    If I write code that depends on assumptions about the current
    platform, it still might not work for some reason. If I write
    portable code in the first place and something goes wrong, I have
    one less thing to worry about as a possible cause of the error.
    --
    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 Sun Jan 11 22:44:30 2026
    From Newsgroup: comp.lang.c

    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    Michael S <already5chosen@yahoo.com> writes:
    [...]
    I mean short of completely crazy things that will make maintainer
    immediately fired?

    Most likely nothing.
    [...]

    Sorry about the duplicate post (server problems).
    --
    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 Sun Jan 11 22:47:40 2026
    From Newsgroup: comp.lang.c

    Michael S <already5chosen@yahoo.com> writes:
    On Sun, 11 Jan 2026 11:51:43 -0800
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
    Michael S <already5chosen@yahoo.com> writes:
    [...]
    Properties are:
    a) uint32_t aliased to 'unsigned long'
    b) 'unsigned int' is at least 32-bit wide.

    It seems unlikely that any implementation would make such a
    choice. Can you name one that does?

    Four out of four target for which I write C programs for living in this decade:
    - Altera Nios2 (nios2-elf-gcc)
    - Arm Cortex-M bare metal (arm-none-eabi-gcc)
    - Win32-i386, various compilers
    - Win64-Amd64,various compilers

    I find that surprising. I just tried a test program that prints
    the name of the type uint32_t is an alias for (using _Generic),
    and it's alias to unsigned int on every implementation I tried.
    (Your properties are limited to systems with 32-bit int and long.)

    For an implementation where int and long are both 32 bits, it
    wouldn't have surprised me for uint32_t to be an alias either for
    unsigned int or for unsigned long, and I wouldn't care either way
    beyond idle curiosity, but all the implementations I've tried choose
    to use unsigned int.

    Well, if I would be pedantic, then in this decade I also wrote several programs for Arm32 Linux, where I don't know whether uint32_t is alias
    of 'unsigned int' or 'unsigned long', few programs for AMD64 Linux,
    where I know that uint32_t is an alias of 'unsigned long' and may be one program for ARM64 Linux that is the same as AMD64 Linux.
    But all those outliers together constitute a tiny fraction of the code
    that I wrote recently.

    One advantage of my approach is that I don't have to know or care
    what the underlying type of uint32_t is.
    --
    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 Mon Jan 12 08:21:43 2026
    From Newsgroup: comp.lang.c

    On 11/01/2026 23:56, Keith Thompson wrote:
    Michael S <already5chosen@yahoo.com> writes:

    The solution that I would prefer would be universal adaption of
    Microsoft's size specifiers I32 and I64. They are not going to win
    beauty competition, but in practice they are a lot more convenient to
    use than standard macros and are equally good at carrying programmer's
    intentions.

    The relative beauty of a feature that isn't available hardly seems
    relevant.

    The ideal solution is to write correct, and preferably portable,
    code in the first place. There are often good reasons to write
    non-portable code, but I suggest that fiding the correct format
    string to be ugly is not one of them.

    (Microsoft's documentation says that "I32" prefix applies to an
    argument of type __int32 or unsigned __int32. I don't know whether
    __int32 is compatible with int, with long, or neither, and I don't
    much care. I don't know what Microsoft guarantees about printf
    with incompatible types that happen to have the same size.)


    C23 includes length specifiers with explicit bit counts, so "%w32u" is
    for an unsigned integer argument of 32 bits:

    """
    wN Specifies that a following b, B, d, i, o, u, x, or X conversion
    specifier applies to an integer argument with a specific width where N
    is a positive decimal integer with no leading zeros (the argument will
    have been promoted according to the integer promotions, but its value
    shall be converted to the unpromoted type); or that a following n
    conversion specifier applies to a pointer to an integer type argument
    with a width of N bits. All minimum-width integer types (7.22.1.2) and exact-width integer types (7.22.1.1) defined in the header <stdint.h>
    shall be supported. Other supported values of N are implementation-defined.
    """

    That looks to me that it would be a correct specifier for uint32_t, and
    should also be fully defined behaviour for unsigned int and unsigned
    long if these are 32 bits wide.




    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Mon Jan 12 08:28:23 2026
    From Newsgroup: comp.lang.c

    On 11/01/2026 20:58, Tim Rentsch wrote:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:

    Michael S <already5chosen@yahoo.com> writes:

    On Sat, 10 Jan 2026 22:02:03 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:

    On 2026-01-09 07:18, Michael S wrote:

    On Thu, 8 Jan 2026 19:31:13 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:

    ...

    I'd have no problem with your approach if you hadn't falsely
    claimed that "It is correct on all platforms".

    Which I didn't.

    On 2026-01-07 19:38, Michael S wrote:
    ...

    No, it is correct on all implementation.

    The quote is taken out of context.
    The context was that on platforms that have properties (a) and (b) (see
    below) printing variables declared as uint32_t via %u is probably UB
    according to the Standard (I don't know for sure, however it is
    probable),

    I'm sure. uint32_t is an alias for some predefined integer type.

    Very likely, but I don't think the C standard requires it. TTBOMU
    the C standard allows the possibility of an implementation where
    uint32_t is type distinct from any other nameable type, and yet
    the implementation could still be conforming.

    gcc for the AVR (or more accurately, the avrlibc library for the AVR
    port of gcc) defines the various <stdint.h> types as "int" or "unsigned
    int" with special modes giving their lengths (using a gcc-specific
    extension). I am never quite sure about the compatibility of these
    types and other identically sized integer types.

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Mon Jan 12 08:34:02 2026
    From Newsgroup: comp.lang.c

    On 12/01/2026 07:47, Keith Thompson wrote:
    Michael S <already5chosen@yahoo.com> writes:
    On Sun, 11 Jan 2026 11:51:43 -0800
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
    Michael S <already5chosen@yahoo.com> writes:
    [...]
    Properties are:
    a) uint32_t aliased to 'unsigned long'
    b) 'unsigned int' is at least 32-bit wide.

    It seems unlikely that any implementation would make such a
    choice. Can you name one that does?

    Four out of four target for which I write C programs for living in this
    decade:
    - Altera Nios2 (nios2-elf-gcc)
    - Arm Cortex-M bare metal (arm-none-eabi-gcc)
    - Win32-i386, various compilers
    - Win64-Amd64,various compilers

    I find that surprising. I just tried a test program that prints
    the name of the type uint32_t is an alias for (using _Generic),
    and it's alias to unsigned int on every implementation I tried.
    (Your properties are limited to systems with 32-bit int and long.)


    On 32-bit embedded systems, it is common for uint32_t to be unsigned
    long, even though unsigned int is the same size. It perhaps comes from
    the pretty much universal definition of uint32_t as unsigned long for
    smaller embedded systems where "int" is less than 32 bits.

    On Linux systems, unsigned int seems more common even on 32-bit systems.
    Thus 32-bit EABI ARM uses unsigned long, while 32-bit Linux ARM uses unsigned int.

    (Common, of course, does not mean guaranteed - there are no doubt
    exceptions to the general pattern.)

    For an implementation where int and long are both 32 bits, it
    wouldn't have surprised me for uint32_t to be an alias either for
    unsigned int or for unsigned long, and I wouldn't care either way
    beyond idle curiosity, but all the implementations I've tried choose
    to use unsigned int.

    Well, if I would be pedantic, then in this decade I also wrote several
    programs for Arm32 Linux, where I don't know whether uint32_t is alias
    of 'unsigned int' or 'unsigned long', few programs for AMD64 Linux,
    where I know that uint32_t is an alias of 'unsigned long' and may be one
    program for ARM64 Linux that is the same as AMD64 Linux.
    But all those outliers together constitute a tiny fraction of the code
    that I wrote recently.

    One advantage of my approach is that I don't have to know or care
    what the underlying type of uint32_t is.


    Indeed.

    (I also write non-portable code where I /do/ know the underlying type -
    so I might use "lu" directly for uint32_t. But I know the code will not
    be used on other targets, and I know I have the correct specifier for
    the target I am using.)


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael S@already5chosen@yahoo.com to comp.lang.c on Mon Jan 12 10:34:48 2026
    From Newsgroup: comp.lang.c

    On Sun, 11 Jan 2026 23:51:04 +0200
    Michael S <already5chosen@yahoo.com> wrote:

    On Sun, 11 Jan 2026 11:51:43 -0800
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:

    On Sat, 10 Jan 2026 22:02:03 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:

    On 2026-01-09 07:18, Michael S wrote:

    On Thu, 8 Jan 2026 19:31:13 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
    wrote:

    ...

    I'd have no problem with your approach if you hadn't falsely
    claimed that "It is correct on all platforms".

    Which I didn't.

    On 2026-01-07 19:38, Michael S wrote:
    ...

    No, it is correct on all implementation.


    The quote is taken out of context.
    The context was that on platforms that have properties (a) and (b)
    (see below) printing variables declared as uint32_t via %u is
    probably UB according to the Standard (I don't know for sure,
    however it is probable), but it can't cause troubles with
    production C compiler. Or with any C compiler that is made in
    intention of being used rather than crafted to prove theoretical
    points. Properties are:
    a) uint32_t aliased to 'unsigned long'
    b) 'unsigned int' is at least 32-bit wide.

    It seems unlikely that any implementation would make such a
    choice. Can you name one that does?

    Four out of four target for which I write C programs for living in
    this decade:
    - Altera Nios2 (nios2-elf-gcc)
    - Arm Cortex-M bare metal (arm-none-eabi-gcc)
    - Win32-i386, various compilers
    - Win64-Amd64,various compilers

    Well, if I would be pedantic, then in this decade I also wrote several programs for Arm32 Linux, where I don't know whether uint32_t is alias
    of 'unsigned int' or 'unsigned long', few programs for AMD64 Linux,
    where I know that uint32_t is an alias of 'unsigned long'

    Cut&past mistake here: read " of 'unsigned int' "

    and may be
    one program for ARM64 Linux that is the same as AMD64 Linux.
    But all those outliers together constitute a tiny fraction of the code
    that I wrote recently.



    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael S@already5chosen@yahoo.com to comp.lang.c on Mon Jan 12 10:50:10 2026
    From Newsgroup: comp.lang.c

    On Sun, 11 Jan 2026 15:23:35 -0800
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:

    To be fair, it is not ideal.
    The solution that I would prefer would be universal adaption of
    Microsoft's size specifiers I32 and I64. They are not going to win
    beauty competition, but in practice they are a lot more convenient
    to use than standard macros and are equally good at carrying
    programmer's intentions.

    The relative beauty of a feature that isn't available hardly seems
    relevant.

    The ideal solution is to write correct, and preferably portable,
    code in the first place. There are often good reasons to write
    non-portable code, but I suggest that fiding the correct format
    string to be ugly is not one of them.

    (Microsoft's documentation says that "I32" prefix applies to an
    argument of type __int32 or unsigned __int32. I don't know whether
    __int32 is compatible with int, with long, or neither, and I don't
    much care. I don't know what Microsoft guarantees about printf
    with incompatible types that happen to have the same size.)


    Microsoft guarantees that __int32 is compatible with int32_t and that
    'unsigned __int32' is compatible with uint32_t. The same goes for 64-bit
    types.
    That's sufficient for safe use of format specifier "%I32u" when
    printing uint32_t variables.

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael S@already5chosen@yahoo.com to comp.lang.c on Mon Jan 12 10:51:43 2026
    From Newsgroup: comp.lang.c

    On Sun, 11 Jan 2026 22:44:30 -0800
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:

    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    Michael S <already5chosen@yahoo.com> writes:
    [...]
    I mean short of completely crazy things that will make maintainer
    immediately fired?

    Most likely nothing.
    [...]

    Sorry about the duplicate post (server problems).


    I had my comp.arch post triplicated for the same reason.

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael S@already5chosen@yahoo.com to comp.lang.c on Mon Jan 12 11:06:48 2026
    From Newsgroup: comp.lang.c

    On Mon, 12 Jan 2026 08:21:43 +0100
    David Brown <david.brown@hesbynett.no> wrote:

    On 11/01/2026 23:56, Keith Thompson wrote:
    Michael S <already5chosen@yahoo.com> writes:

    The solution that I would prefer would be universal adaption of
    Microsoft's size specifiers I32 and I64. They are not going to win
    beauty competition, but in practice they are a lot more convenient
    to use than standard macros and are equally good at carrying
    programmer's intentions.

    The relative beauty of a feature that isn't available hardly seems relevant.

    The ideal solution is to write correct, and preferably portable,
    code in the first place. There are often good reasons to write non-portable code, but I suggest that fiding the correct format
    string to be ugly is not one of them.

    (Microsoft's documentation says that "I32" prefix applies to an
    argument of type __int32 or unsigned __int32. I don't know whether
    __int32 is compatible with int, with long, or neither, and I don't
    much care. I don't know what Microsoft guarantees about printf
    with incompatible types that happen to have the same size.)


    C23 includes length specifiers with explicit bit counts, so "%w32u"
    is for an unsigned integer argument of 32 bits:

    """
    wN Specifies that a following b, B, d, i, o, u, x, or X conversion
    specifier applies to an integer argument with a specific width where
    N is a positive decimal integer with no leading zeros (the argument
    will have been promoted according to the integer promotions, but its
    value shall be converted to the unpromoted type); or that a following
    n conversion specifier applies to a pointer to an integer type
    argument with a width of N bits. All minimum-width integer types
    (7.22.1.2) and exact-width integer types (7.22.1.1) defined in the
    header <stdint.h> shall be supported. Other supported values of N are implementation-defined. """

    That looks to me that it would be a correct specifier for uint32_t,
    and should also be fully defined behaviour for unsigned int and
    unsigned long if these are 32 bits wide.


    It sounds very good.

    Except that none of my four targets of major interest supports C23 at
    the moment. Esp. so at the level of standard library.

    For one of them (Nios2) in the absence of something VERY unexpected
    there never be support (gcc stopped support for Nios2 2 or 3 years ago).

    For the other three, it will take time. I can't even guess how long,
    except that I know that support versions of arm-none-eabi-gcc lags two
    years behind "hosted" x86-64 and ARM64 versions, so I can guess that it
    would take significant time to catch up.






    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael S@already5chosen@yahoo.com to comp.lang.c on Mon Jan 12 11:24:09 2026
    From Newsgroup: comp.lang.c

    On Sun, 11 Jan 2026 22:47:40 -0800
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:
    On Sun, 11 Jan 2026 11:51:43 -0800
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
    Michael S <already5chosen@yahoo.com> writes:
    [...]
    Properties are:
    a) uint32_t aliased to 'unsigned long'
    b) 'unsigned int' is at least 32-bit wide.

    It seems unlikely that any implementation would make such a
    choice. Can you name one that does?

    Four out of four target for which I write C programs for living in
    this decade:
    - Altera Nios2 (nios2-elf-gcc)
    - Arm Cortex-M bare metal (arm-none-eabi-gcc)
    - Win32-i386, various compilers
    - Win64-Amd64,various compilers

    I find that surprising. I just tried a test program that prints
    the name of the type uint32_t is an alias for (using _Generic),
    and it's alias to unsigned int on every implementation I tried.
    (Your properties are limited to systems with 32-bit int and long.)

    For an implementation where int and long are both 32 bits, it
    wouldn't have surprised me for uint32_t to be an alias either for
    unsigned int or for unsigned long, and I wouldn't care either way
    beyond idle curiosity, but all the implementations I've tried choose
    to use unsigned int.


    Did you try any implementation that is not based on SysV ABI?

    Well, if I would be pedantic, then in this decade I also wrote
    several programs for Arm32 Linux, where I don't know whether
    uint32_t is alias of 'unsigned int' or 'unsigned long', few
    programs for AMD64 Linux, where I know that uint32_t is an alias of 'unsigned long' and may be one program for ARM64 Linux that is the
    same as AMD64 Linux. But all those outliers together constitute a
    tiny fraction of the code that I wrote recently.

    One advantage of my approach is that I don't have to know or care
    what the underlying type of uint32_t is.


    By 'you approach' you mean casting to 'unsigned long' and using %ld
    formatter?
    As I said in other post, ideologically I like it. The only reason I
    don't adapt that approach myself is because 'unsigned long' is long
    (many characters).
    I'd do exactly that in case of int32_t, except that in practice
    int32_t is very rare in my code and printing it rarer still.
    [O.T.]
    When I can, I try to use signed integer types in preference of unsigned
    types, but those are mostly plain 'int' and ptrdiff_t, occasionally
    int16_t, rarely 'long'. int32_t and int64_t are very rare.












    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Mon Jan 12 13:26:40 2026
    From Newsgroup: comp.lang.c

    On 12/01/2026 10:06, Michael S wrote:
    On Mon, 12 Jan 2026 08:21:43 +0100
    David Brown <david.brown@hesbynett.no> wrote:

    On 11/01/2026 23:56, Keith Thompson wrote:
    Michael S <already5chosen@yahoo.com> writes:

    The solution that I would prefer would be universal adaption of
    Microsoft's size specifiers I32 and I64. They are not going to win
    beauty competition, but in practice they are a lot more convenient
    to use than standard macros and are equally good at carrying
    programmer's intentions.

    The relative beauty of a feature that isn't available hardly seems
    relevant.

    The ideal solution is to write correct, and preferably portable,
    code in the first place. There are often good reasons to write
    non-portable code, but I suggest that fiding the correct format
    string to be ugly is not one of them.

    (Microsoft's documentation says that "I32" prefix applies to an
    argument of type __int32 or unsigned __int32. I don't know whether
    __int32 is compatible with int, with long, or neither, and I don't
    much care. I don't know what Microsoft guarantees about printf
    with incompatible types that happen to have the same size.)


    C23 includes length specifiers with explicit bit counts, so "%w32u"
    is for an unsigned integer argument of 32 bits:

    """
    wN Specifies that a following b, B, d, i, o, u, x, or X conversion
    specifier applies to an integer argument with a specific width where
    N is a positive decimal integer with no leading zeros (the argument
    will have been promoted according to the integer promotions, but its
    value shall be converted to the unpromoted type); or that a following
    n conversion specifier applies to a pointer to an integer type
    argument with a width of N bits. All minimum-width integer types
    (7.22.1.2) and exact-width integer types (7.22.1.1) defined in the
    header <stdint.h> shall be supported. Other supported values of N are
    implementation-defined. """

    That looks to me that it would be a correct specifier for uint32_t,
    and should also be fully defined behaviour for unsigned int and
    unsigned long if these are 32 bits wide.


    It sounds very good.

    Except that none of my four targets of major interest supports C23 at
    the moment. Esp. so at the level of standard library.


    gcc has supported the format, along with much of C23, since gcc 13, and
    ARM's gcc-based toolchain version 13.2 is from October 2023. (The
    current version is 15.2 from December 2025.) But I don't know about
    library support - that is a very different matter. (Compiler support
    for printf really just means checking the format specifiers match the parameters.)

    It is an unfortunate fact of developers' lives that we see fun new
    features in new language standards, but often have to wait years before
    we can play with them.

    For one of them (Nios2) in the absence of something VERY unexpected
    there never be support (gcc stopped support for Nios2 2 or 3 years ago).

    For the other three, it will take time. I can't even guess how long,
    except that I know that support versions of arm-none-eabi-gcc lags two
    years behind "hosted" x86-64 and ARM64 versions, so I can guess that it
    would take significant time to catch up.


    ARM's gcc-based toolchain builds are not nearly as far behind as they
    used to be. They have pretty much caught up to current stable gcc releases.

    <https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads>


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Mon Jan 12 04:27:14 2026
    From Newsgroup: comp.lang.c

    David Brown <david.brown@hesbynett.no> writes:
    [...]
    C23 includes length specifiers with explicit bit counts, so "%w32u" is
    for an unsigned integer argument of 32 bits:

    """
    wN Specifies that a following b, B, d, i, o, u, x, or X conversion
    specifier applies to an integer argument with a specific width
    where N is a positive decimal integer with no leading zeros
    (the argument will have been promoted according to the integer
    promotions, but its value shall be converted to the unpromoted
    type); or that a following n conversion specifier applies to a
    pointer to an integer type argument with a width of N bits. All
    minimum-width integer types (7.22.1.2) and exact-width integer
    types (7.22.1.1) defined in the header <stdint.h> shall be
    supported. Other supported values of N are implementation-defined.
    """

    That looks to me that it would be a correct specifier for uint32_t,

    Yes, so for example this:

    uint32_t n = 42;
    printf("n = %w32u\n", n);

    is correct, if I'm reading it correctly. It's also correct for
    uint_least32_t, which is expected to be the same type as uint32_t
    if the latter exists. There's also support for the [u]int_fastN_t
    types, using for example "%wf32u" in place of "%w32u".

    and should also be fully defined behaviour for unsigned int and
    unsigned long if these are 32 bits wide.

    No, I don't think C23 says that. If int and long happen to be the same
    width, they are still incompatible, and there is no printf format
    specifier that has defined behavior for both.

    That first sentence is a bit ambiguous

    wN Specifies that a following b, B, d, i, o, u, x, or X conversion
    specifier applies to an integer argument with a specific width ...

    but I don't think it means that it must accept *any* integer type
    of the specified width.

    Later in the same paragraph, it says that all [u]intN_t and
    [u]int_leastN_t types shall be supported -- all such *types*, not
    all such *widths*. And it doesn't say that the predefined types
    shall be supported.

    Paragraph 9 says:

    fprintf shall behave as if it uses va_arg with a type argument
    naming the type resulting from applying the default argument
    promotions to the type corresponding to the conversion specification
    and then converting the result of the va_arg expansion to the type
    corresponding to the conversion specification.

    And in the description for the va_arg macro (whose second argument
    is a type name):

    If *type* is not compatible with the type of the actual
    next argument (as promoted according to the default argument
    promotions), the behavior is undefined, except for the following
    cases: ...

    Corresponding signed and unsigned types are supported if the value
    is representable in both, but there's no provision for mixing int
    and long even if they have the same width.

    If printf is implemented using <stdarg.h>, what type name can it
    pass to the va_arg() macro given a "%w32u" specification? It can
    only pass uint32_t or uint_least32_t (or it can pass unsigned int
    or unsigned long *if* that type is compatible with the uint32_t).
    (And C23 adds a requirement that [u]int_leastN_t is the same type as
    [u]intN_t if the latter exists; perhaps this is why.)

    Prior to C17, there is no conversion specification that's valid for
    both int and long, even if they're the same width. Changing that
    in C23 would have been a significant change, but there's no mention
    of it, even in a footnote.

    The "%w..." format specifiers are simpler (and IMHO less ugly)
    than the macros in <inttypes.h>, but they don't add any fundamental
    new capability.

    Given the format specifier "%w32u", the corresponding argument must
    be of type uint32_t, or it can be of type int32_t and representable
    in both, or it can be of a type compatible with [u]int32_t. I expect
    that in most or all implementations, the undefined behavior of
    passing an incompatible type with the same width and representation
    will appear as if it "worked", but a compile-time warning is likely
    if the format is a string literal.

    And of course if you want to print a uint32_t value, you can always
    cast it to unsigned long and use "%u".
    --
    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 Mon Jan 12 17:57:23 2026
    From Newsgroup: comp.lang.c

    On 12/01/2026 13:27, Keith Thompson wrote:
    David Brown <david.brown@hesbynett.no> writes:
    [...]
    C23 includes length specifiers with explicit bit counts, so "%w32u" is
    for an unsigned integer argument of 32 bits:

    """
    wN Specifies that a following b, B, d, i, o, u, x, or X conversion
    specifier applies to an integer argument with a specific width
    where N is a positive decimal integer with no leading zeros
    (the argument will have been promoted according to the integer
    promotions, but its value shall be converted to the unpromoted
    type); or that a following n conversion specifier applies to a
    pointer to an integer type argument with a width of N bits. All
    minimum-width integer types (7.22.1.2) and exact-width integer
    types (7.22.1.1) defined in the header <stdint.h> shall be
    supported. Other supported values of N are implementation-defined.
    """

    That looks to me that it would be a correct specifier for uint32_t,

    Yes, so for example this:

    uint32_t n = 42;
    printf("n = %w32u\n", n);

    is correct, if I'm reading it correctly. It's also correct for uint_least32_t, which is expected to be the same type as uint32_t
    if the latter exists. There's also support for the [u]int_fastN_t
    types, using for example "%wf32u" in place of "%w32u".

    and should also be fully defined behaviour for unsigned int and
    unsigned long if these are 32 bits wide.

    No, I don't think C23 says that. If int and long happen to be the same width, they are still incompatible, and there is no printf format
    specifier that has defined behavior for both.

    That first sentence is a bit ambiguous

    wN Specifies that a following b, B, d, i, o, u, x, or X conversion
    specifier applies to an integer argument with a specific width ...

    but I don't think it means that it must accept *any* integer type
    of the specified width.

    That's the part that I am not at all sure about - it is, as you say, ambiguous. It also refers to "a pointer to an integer type argument
    with a width of N bits" (in the context of an "n" specifier) - that
    sounds like "%w32n" would be happy with a "uint32_t *", "unsigned int *"
    or "unsigned long int *" pointer (when the integer types are all 32
    bit). It would be strange for printf to be happy with clearly
    incompatible pointer types when the integer sizes are the same, but not
    be happy with integer values when the sizes are the same.

    But my interpretation here could well be wrong. Only the [u]intNN_t and [u]int_leastNN_t types are explicitly and clearly accepted here.


    Later in the same paragraph, it says that all [u]intN_t and
    [u]int_leastN_t types shall be supported -- all such *types*, not
    all such *widths*. And it doesn't say that the predefined types
    shall be supported.

    Paragraph 9 says:

    fprintf shall behave as if it uses va_arg with a type argument
    naming the type resulting from applying the default argument
    promotions to the type corresponding to the conversion specification
    and then converting the result of the va_arg expansion to the type
    corresponding to the conversion specification.

    And in the description for the va_arg macro (whose second argument
    is a type name):

    If *type* is not compatible with the type of the actual
    next argument (as promoted according to the default argument
    promotions), the behavior is undefined, except for the following
    cases: ...

    Corresponding signed and unsigned types are supported if the value
    is representable in both, but there's no provision for mixing int
    and long even if they have the same width.

    If printf is implemented using <stdarg.h>, what type name can it
    pass to the va_arg() macro given a "%w32u" specification? It can
    only pass uint32_t or uint_least32_t (or it can pass unsigned int
    or unsigned long *if* that type is compatible with the uint32_t).
    (And C23 adds a requirement that [u]int_leastN_t is the same type as [u]intN_t if the latter exists; perhaps this is why.)

    Prior to C17, there is no conversion specification that's valid for
    both int and long, even if they're the same width. Changing that
    in C23 would have been a significant change, but there's no mention
    of it, even in a footnote.

    The "%w..." format specifiers are simpler (and IMHO less ugly)
    than the macros in <inttypes.h>, but they don't add any fundamental
    new capability.

    Given the format specifier "%w32u", the corresponding argument must
    be of type uint32_t, or it can be of type int32_t and representable
    in both, or it can be of a type compatible with [u]int32_t. I expect
    that in most or all implementations, the undefined behavior of
    passing an incompatible type with the same width and representation
    will appear as if it "worked", but a compile-time warning is likely
    if the format is a string literal.

    And of course if you want to print a uint32_t value, you can always
    cast it to unsigned long and use "%u".


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Mon Jan 12 16:06:08 2026
    From Newsgroup: comp.lang.c

    David Brown <david.brown@hesbynett.no> writes:
    [...]

    Context: %wN and %wfN printf length modifier, new in C23.

    gcc has supported the format, along with much of C23, since gcc 13,
    and ARM's gcc-based toolchain version 13.2 is from October 2023. (The current version is 15.2 from December 2025.) But I don't know about
    library support - that is a very different matter. (Compiler support
    for printf really just means checking the format specifiers match the parameters.)

    Of course printf is implemented in the library, not in the compiler.

    gcc has had format checking for %wN and %wfN since release 13, but
    that's useless in the absence of library support.

    Support in glibc was added 2023-06-19 and released in version 2.39.
    Other C library implementations may or may not support it.

    [...]
    --
    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 Tue Jan 13 08:56:45 2026
    From Newsgroup: comp.lang.c

    On 13/01/2026 01:06, Keith Thompson wrote:
    David Brown <david.brown@hesbynett.no> writes:
    [...]

    Context: %wN and %wfN printf length modifier, new in C23.

    gcc has supported the format, along with much of C23, since gcc 13,
    and ARM's gcc-based toolchain version 13.2 is from October 2023. (The
    current version is 15.2 from December 2025.) But I don't know about
    library support - that is a very different matter. (Compiler support
    for printf really just means checking the format specifiers match the
    parameters.)

    Of course printf is implemented in the library, not in the compiler.

    Primarily, yes. But like all standard library functions, compilers can
    have special handling in some ways. This is more obvious for functions
    like memcpy, where the compiler can often generate significantly better
    code (specially for small known sizes). As far as I know, the only optimisation gcc does on printf is turn something like printf("Hello\n")
    into puts("Hello"). Hypothetically, there is nothing to stop a compiler
    being a great deal more sophisticated than that, and doing the
    format-string interpretation directly in some way.


    gcc has had format checking for %wN and %wfN since release 13, but
    that's useless in the absence of library support.

    Yes.


    Support in glibc was added 2023-06-19 and released in version 2.39.
    Other C library implementations may or may not support it.


    glibc is not particularly relevant for non-Linux embedded systems.
    newlib (and newlib-nano) are a common choice for such systems, but I
    have no idea if it currently has support for those formats.


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Tue Jan 13 00:53:36 2026
    From Newsgroup: comp.lang.c

    David Brown <david.brown@hesbynett.no> writes:
    On 13/01/2026 01:06, Keith Thompson wrote:
    David Brown <david.brown@hesbynett.no> writes:
    [...]
    Context: %wN and %wfN printf length modifier, new in C23.

    gcc has supported the format, along with much of C23, since gcc 13,
    and ARM's gcc-based toolchain version 13.2 is from October 2023. (The
    current version is 15.2 from December 2025.) But I don't know about
    library support - that is a very different matter. (Compiler support
    for printf really just means checking the format specifiers match the
    parameters.)
    Of course printf is implemented in the library, not in the compiler.

    Primarily, yes. But like all standard library functions, compilers
    can have special handling in some ways. This is more obvious for
    functions like memcpy, where the compiler can often generate
    significantly better code (specially for small known sizes). As far
    as I know, the only optimisation gcc does on printf is turn something
    like printf("Hello\n") into puts("Hello"). Hypothetically, there is
    nothing to stop a compiler being a great deal more sophisticated than
    that, and doing the format-string interpretation directly in some way.

    Sure, but in practice the library support for printf is the only thing
    that matters. If your library doesn't support %wN, having the compiler recognize it doesn't help. I'm not sure why you even mentioned gcc in
    this context.

    gcc has had format checking for %wN and %wfN since release 13, but
    that's useless in the absence of library support.

    Yes.

    Support in glibc was added 2023-06-19 and released in version 2.39.
    Other C library implementations may or may not support it.

    glibc is not particularly relevant for non-Linux embedded
    systems. newlib (and newlib-nano) are a common choice for such
    systems, but I have no idea if it currently has support for those
    formats.

    Of course, that's why I mentioned other C library implementations.
    glibc happens to be the one about which I had some relevant
    information.

    The versions of musl and dietlibc that I have on my Ubuntu system
    don't support %wN. The version of newlib I have on Cygwin also
    doesn't support it. PellesC on Windows does.
    --
    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 Tue Jan 13 11:09:55 2026
    From Newsgroup: comp.lang.c

    On 13/01/2026 09:53, Keith Thompson wrote:
    David Brown <david.brown@hesbynett.no> writes:
    On 13/01/2026 01:06, Keith Thompson wrote:
    David Brown <david.brown@hesbynett.no> writes:
    [...]
    Context: %wN and %wfN printf length modifier, new in C23.

    gcc has supported the format, along with much of C23, since gcc 13,
    and ARM's gcc-based toolchain version 13.2 is from October 2023. (The >>>> current version is 15.2 from December 2025.) But I don't know about
    library support - that is a very different matter. (Compiler support
    for printf really just means checking the format specifiers match the
    parameters.)
    Of course printf is implemented in the library, not in the compiler.

    Primarily, yes. But like all standard library functions, compilers
    can have special handling in some ways. This is more obvious for
    functions like memcpy, where the compiler can often generate
    significantly better code (specially for small known sizes). As far
    as I know, the only optimisation gcc does on printf is turn something
    like printf("Hello\n") into puts("Hello"). Hypothetically, there is
    nothing to stop a compiler being a great deal more sophisticated than
    that, and doing the format-string interpretation directly in some way.

    Sure, but in practice the library support for printf is the only thing
    that matters. If your library doesn't support %wN, having the compiler recognize it doesn't help. I'm not sure why you even mentioned gcc in
    this context.


    I had several reasons (I would want compiler checking of the format
    before using it, I know the state of support in gcc, and I had mentioned
    ARM's gcc toolchains as they are the standard choice of toolchains for embedded ARM systems). But I had not intended it to be the focus, since library support is the critical point. (newlibc, as far as I can tell,
    does not yet support it.)

    gcc has had format checking for %wN and %wfN since release 13, but
    that's useless in the absence of library support.

    Yes.

    Support in glibc was added 2023-06-19 and released in version 2.39.
    Other C library implementations may or may not support it.

    glibc is not particularly relevant for non-Linux embedded
    systems. newlib (and newlib-nano) are a common choice for such
    systems, but I have no idea if it currently has support for those
    formats.

    Of course, that's why I mentioned other C library implementations.
    glibc happens to be the one about which I had some relevant
    information.


    Fair enough.

    The versions of musl and dietlibc that I have on my Ubuntu system
    don't support %wN. The version of newlib I have on Cygwin also
    doesn't support it. PellesC on Windows does.


    musl and dietlibc are not suitable for non-Linux embedded systems
    either, but of course it is nice to see the progress of support in
    different libraries for different systems. (And newlibc doesn't support
    it yet - at least, not according to the online documentation.)

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael S@already5chosen@yahoo.com to comp.lang.c on Tue Jan 13 13:45:01 2026
    From Newsgroup: comp.lang.c

    On Tue, 13 Jan 2026 11:09:55 +0100
    David Brown <david.brown@hesbynett.no> wrote:

    On 13/01/2026 09:53, Keith Thompson wrote:
    David Brown <david.brown@hesbynett.no> writes:
    On 13/01/2026 01:06, Keith Thompson wrote:
    David Brown <david.brown@hesbynett.no> writes:
    [...]
    Context: %wN and %wfN printf length modifier, new in C23.

    gcc has supported the format, along with much of C23, since gcc
    13, and ARM's gcc-based toolchain version 13.2 is from October
    2023. (The current version is 15.2 from December 2025.) But I
    don't know about library support - that is a very different
    matter. (Compiler support for printf really just means checking
    the format specifiers match the parameters.)
    Of course printf is implemented in the library, not in the
    compiler.

    Primarily, yes. But like all standard library functions, compilers
    can have special handling in some ways. This is more obvious for
    functions like memcpy, where the compiler can often generate
    significantly better code (specially for small known sizes). As
    far as I know, the only optimisation gcc does on printf is turn
    something like printf("Hello\n") into puts("Hello").
    Hypothetically, there is nothing to stop a compiler being a great
    deal more sophisticated than that, and doing the format-string
    interpretation directly in some way.

    Sure, but in practice the library support for printf is the only
    thing that matters. If your library doesn't support %wN, having
    the compiler recognize it doesn't help. I'm not sure why you even mentioned gcc in this context.


    I had several reasons (I would want compiler checking of the format
    before using it, I know the state of support in gcc, and I had
    mentioned ARM's gcc toolchains as they are the standard choice of
    toolchains for embedded ARM systems).

    [O.T.]
    AFAIK, that's no longer the case. For last 3-4 years Cortex-M MCU
    vendors, in particular STMicro and TI, intensely push clang as a default compiler in their free IDEs.
    Today, in order to use gcc in the new embedded ARM project, developer
    has to make a conscious effort of rejecting vendor's default. Most
    likely, gcc compiler does not come as part of default installation
    package. It has to be downloaded and installed separately.
    I would guess that overwhelming majority of devs does not bother.

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Tue Jan 13 14:32:45 2026
    From Newsgroup: comp.lang.c

    On 13/01/2026 12:45, Michael S wrote:
    On Tue, 13 Jan 2026 11:09:55 +0100
    David Brown <david.brown@hesbynett.no> wrote:

    On 13/01/2026 09:53, Keith Thompson wrote:
    David Brown <david.brown@hesbynett.no> writes:
    On 13/01/2026 01:06, Keith Thompson wrote:
    David Brown <david.brown@hesbynett.no> writes:
    [...]
    Context: %wN and %wfN printf length modifier, new in C23.

    gcc has supported the format, along with much of C23, since gcc
    13, and ARM's gcc-based toolchain version 13.2 is from October
    2023. (The current version is 15.2 from December 2025.) But I
    don't know about library support - that is a very different
    matter. (Compiler support for printf really just means checking
    the format specifiers match the parameters.)
    Of course printf is implemented in the library, not in the
    compiler.

    Primarily, yes. But like all standard library functions, compilers
    can have special handling in some ways. This is more obvious for
    functions like memcpy, where the compiler can often generate
    significantly better code (specially for small known sizes). As
    far as I know, the only optimisation gcc does on printf is turn
    something like printf("Hello\n") into puts("Hello").
    Hypothetically, there is nothing to stop a compiler being a great
    deal more sophisticated than that, and doing the format-string
    interpretation directly in some way.

    Sure, but in practice the library support for printf is the only
    thing that matters. If your library doesn't support %wN, having
    the compiler recognize it doesn't help. I'm not sure why you even
    mentioned gcc in this context.


    I had several reasons (I would want compiler checking of the format
    before using it, I know the state of support in gcc, and I had
    mentioned ARM's gcc toolchains as they are the standard choice of
    toolchains for embedded ARM systems).

    [O.T.]
    AFAIK, that's no longer the case. For last 3-4 years Cortex-M MCU
    vendors, in particular STMicro and TI, intensely push clang as a default compiler in their free IDEs.

    As far as I know, ST uses ARM's gcc build as their normal toolchain. I haven't looked at TI's development tools for a good while.

    But it is certainly the case that clang-based toolchains are gaining in popularity for non-Linux embedded ARM. I've been looking at them
    myself, and see advantages and disadvantages. However, I believe gcc is
    still dominated by a large margin, and ARM makes regular releases of
    complete toolchain packages (compiler, libraries, debugger, etc.).

    Today, in order to use gcc in the new embedded ARM project, developer
    has to make a conscious effort of rejecting vendor's default. Most
    likely, gcc compiler does not come as part of default installation
    package. It has to be downloaded and installed separately.
    I would guess that overwhelming majority of devs does not bother.


    My experience is that the majority of microcontroller vendors provide
    gcc toolchains as part of their default installations. They used to
    have their own gcc toolchain builds, or use third-parties like Code
    Sourcery, but these days they usually use an off-the-shelf ARM package.
    But several vendors are in a transition period where they are gradually
    moving from established Eclipse-based tools towards VS Code tools, and
    perhaps clang is either an option or default with their newer IDEs.
    Those vendors provide, support and maintain both IDEs at the moment, but sometimes detailed support for particular microcontrollers only exists
    for one of them.

    I agree that most developers will use whatever compiler comes as default
    with their vendor-supplied IDEs. Personally, I think that's fine for
    getting started, or for quick throw-away projects. For anything serious
    I prefer to have the build controlled from outside the IDE (by a
    makefile), using the toolchain I choose (typically the latest ARM gcc toolchain when the project starts, then remaining consistent throughout
    the lifetime of the project). The IDEs can still be good editors, IDEs, debuggers, etc.

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael S@already5chosen@yahoo.com to comp.lang.c on Tue Jan 13 17:47:27 2026
    From Newsgroup: comp.lang.c

    On Tue, 13 Jan 2026 14:32:45 +0100
    David Brown <david.brown@hesbynett.no> wrote:

    On 13/01/2026 12:45, Michael S wrote:
    On Tue, 13 Jan 2026 11:09:55 +0100
    David Brown <david.brown@hesbynett.no> wrote:

    On 13/01/2026 09:53, Keith Thompson wrote:
    David Brown <david.brown@hesbynett.no> writes:
    On 13/01/2026 01:06, Keith Thompson wrote:
    David Brown <david.brown@hesbynett.no> writes:
    [...]
    Context: %wN and %wfN printf length modifier, new in C23.

    gcc has supported the format, along with much of C23, since gcc
    13, and ARM's gcc-based toolchain version 13.2 is from October
    2023. (The current version is 15.2 from December 2025.) But I
    don't know about library support - that is a very different
    matter. (Compiler support for printf really just means
    checking the format specifiers match the parameters.)
    Of course printf is implemented in the library, not in the
    compiler.

    Primarily, yes. But like all standard library functions,
    compilers can have special handling in some ways. This is more
    obvious for functions like memcpy, where the compiler can often
    generate significantly better code (specially for small known
    sizes). As far as I know, the only optimisation gcc does on
    printf is turn something like printf("Hello\n") into
    puts("Hello"). Hypothetically, there is nothing to stop a
    compiler being a great deal more sophisticated than that, and
    doing the format-string interpretation directly in some way.

    Sure, but in practice the library support for printf is the only
    thing that matters. If your library doesn't support %wN, having
    the compiler recognize it doesn't help. I'm not sure why you even
    mentioned gcc in this context.


    I had several reasons (I would want compiler checking of the format
    before using it, I know the state of support in gcc, and I had
    mentioned ARM's gcc toolchains as they are the standard choice of
    toolchains for embedded ARM systems).

    [O.T.]
    AFAIK, that's no longer the case. For last 3-4 years Cortex-M MCU
    vendors, in particular STMicro and TI, intensely push clang as a
    default compiler in their free IDEs.

    As far as I know, ST uses ARM's gcc build as their normal toolchain.
    I haven't looked at TI's development tools for a good while.


    You are right.
    I downloaded the latest version of ST CubeIDE (2.0.0).
    It claims to support STARM-Clang (not that I figured out how exactly),
    but gcc is a default toolchain, same like in 1.x
    I played with it for a few minutes then uninstalled as fast as I can.

    But it is certainly the case that clang-based toolchains are gaining
    in popularity for non-Linux embedded ARM. I've been looking at them
    myself, and see advantages and disadvantages. However, I believe gcc
    is still dominated by a large margin, and ARM makes regular releases
    of complete toolchain packages (compiler, libraries, debugger, etc.).

    Today, in order to use gcc in the new embedded ARM project,
    developer has to make a conscious effort of rejecting vendor's
    default. Most likely, gcc compiler does not come as part of default installation package. It has to be downloaded and installed
    separately. I would guess that overwhelming majority of devs does
    not bother.

    My experience is that the majority of microcontroller vendors provide
    gcc toolchains as part of their default installations. They used to
    have their own gcc toolchain builds, or use third-parties like Code Sourcery, but these days they usually use an off-the-shelf ARM
    package. But several vendors are in a transition period where they
    are gradually moving from established Eclipse-based tools towards VS
    Code tools, and perhaps clang is either an option or default with
    their newer IDEs. Those vendors provide, support and maintain both
    IDEs at the moment, but sometimes detailed support for particular microcontrollers only exists for one of them.

    I agree that most developers will use whatever compiler comes as
    default with their vendor-supplied IDEs. Personally, I think that's
    fine for getting started, or for quick throw-away projects. For
    anything serious I prefer to have the build controlled from outside
    the IDE (by a makefile), using the toolchain I choose (typically the
    latest ARM gcc toolchain when the project starts, then remaining
    consistent throughout the lifetime of the project). The IDEs can
    still be good editors, IDEs, debuggers, etc.


    For me Eclipse-based IDE is slower and harder to use than command line
    in any situation.
    Although I am ready to admit that TI did better job than most of
    turning Eclipse into something almost palatable.
    Still, even Eclipse in form of TI's CCS is an obstacle for me rather
    than help.

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From James Russell Kuyper Jr.@jameskuyper@alumni.caltech.edu to comp.lang.c on Tue Jan 13 21:24:16 2026
    From Newsgroup: comp.lang.c

    On 2026-01-11 08:32, Michael S wrote:
    On Sun, 11 Jan 2026 04:59:47 -0800
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:
    On Sat, 10 Jan 2026 22:02:03 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
    On 2026-01-09 07:18, Michael S wrote:
    On Thu, 8 Jan 2026 19:31:13 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
    wrote:
    ...
    I'd have no problem with your approach if you hadn't falsely
    claimed that "It is correct on all platforms".

    Which I didn't.

    On 2026-01-07 19:38, Michael S wrote:
    ...
    > No, it is correct on all implementation.

    The quote is taken out of context.
    The context was that on platforms that have properties (a) and (b)
    (see below) printing variables declared as uint32_t via %u is
    probably UB according to the Standard (I don't know for sure,
    however it is probable),

    I'm sure. uint32_t is an alias for some predefined integer type.

    This:
    uint32_t n = 42;
    printf("%u\n", n);
    has undefined behavior *unless* uint32_t happens to an alias for
    unsigned int in the current implementation -- not just any 32-bit
    unsigned integer type, only unsigned int.

    If uint32_t is an alias for unsigned long (which implies that
    unsigned long is exactly 32 bits), then the call's behavior is
    undefined. (It might happen to "work".)


    What exactly, assuming that conditions (a) and (b) fulfilled, should implementation do to prevent it from working?
    I mean short of completely crazy things that will make maintainer
    immediately fired?

    I'm quite positive that you would consider anything that might give
    unexpected behavior to such code to be "crazy". The simplest example I
    can think of is that unsigned int is big-endian, while unsigned long is little-endian, and I would even agree that such an implementation would
    be peculiar, but such an implementation could be fully conforming to the
    C standard.

    If uint32_t and unsigned long have different sizes, it still might
    happen happen to "work", depending on calling conventions. Passing a
    32-bit argument and telling printf to expect a 64-bit value clearly
    has undefined behavior, but perhaps both happen to be passed in 64-bit
    registers, for example.


    And that is sort of intimate knowledge of the ABI that I don't want to exploit, as already mentioned in my other post in this sub-thread.

    Which is precisely what's wrong about your approach - it relies upon
    intimate knowledge of the ABI. Specifically, it relies on unsigned int
    and unsigned long happening to have exactly the same size and
    representation.

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From James Russell Kuyper Jr.@jameskuyper@alumni.caltech.edu to comp.lang.c on Tue Jan 13 22:17:09 2026
    From Newsgroup: comp.lang.c

    On 2026-01-11 06:20, Michael S wrote:
    On Sat, 10 Jan 2026 22:02:03 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:

    On 2026-01-09 07:18, Michael S wrote:
    On Thu, 8 Jan 2026 19:31:13 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
    ...
    I'd have no problem with your approach if you hadn't falsely
    claimed that "It is correct on all platforms".

    Which I didn't.

    On 2026-01-07 19:38, Michael S wrote:
    ...
    No, it is correct on all implementation.


    The quote is taken out of context.
    The context was that on platforms that have properties (a) and (b) (see below) printing variables declared as uint32_t via %u is probably UB according to the Standard (I don't know for sure, however it is
    probable), but it can't cause troubles with production C compiler. Or
    with any C compiler that is made in intention of being used rather than crafted to prove theoretical points.
    Properties are:
    a) uint32_t aliased to 'unsigned long'
    b) 'unsigned int' is at least 32-bit wide.

    I never claimed that it is good idea on targets with 'unsigned int'
    that is narrower.

    I've looked for a previous restriction of this discussion to cases
    covered by a) and b) above. The closest I could find is the following:

    In the case I am talking about n declared as uint32_t.
    uint32_t is an alias of 'unsigned long' on 32-bit embedded targets, on
    32-bit Linux, on 32-bit Windows and on 64-bit Windows. It is
    alias of 'unsigned int' on 64-bit Linux.

    Note several points: that is a period after the first use of "uint32_t",
    so "the case" you're specifying ends there. I read the next three lines
    as information about your working environment, not restrictions on the
    claimed validity of your preference for "%u" over "%lu". There is no
    mention of a restriction on the size of "unsigned int".


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Wed Jan 14 09:26:39 2026
    From Newsgroup: comp.lang.c

    On 14/01/2026 03:24, James Russell Kuyper Jr. wrote:
    On 2026-01-11 08:32, Michael S wrote:
    On Sun, 11 Jan 2026 04:59:47 -0800
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:
    On Sat, 10 Jan 2026 22:02:03 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
    On 2026-01-09 07:18, Michael S wrote:
    On Thu, 8 Jan 2026 19:31:13 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
    wrote:
    ...
    I'd have no problem with your approach if you hadn't falsely
    claimed that "It is correct on all platforms".

    Which I didn't.

    On 2026-01-07 19:38, Michael S wrote:
    ...
      > No, it is correct on all implementation.

    The quote is taken out of context.
    The context was that on platforms that have properties (a) and (b)
    (see below) printing variables declared as uint32_t via %u is
    probably UB according to the Standard (I don't know for sure,
    however it is probable),

    I'm sure.  uint32_t is an alias for some predefined integer type.

    This:
         uint32_t n = 42;
         printf("%u\n", n);
    has undefined behavior *unless* uint32_t happens to an alias for
    unsigned int in the current implementation -- not just any 32-bit
    unsigned integer type, only unsigned int.

    If uint32_t is an alias for unsigned long (which implies that
    unsigned long is exactly 32 bits), then the call's behavior is
    undefined.  (It might happen to "work".)


    What exactly, assuming that conditions (a) and (b) fulfilled, should
    implementation do to prevent it from working?
    I mean short of completely crazy things that will make maintainer
    immediately fired?

    I'm quite positive that you would consider anything that might give unexpected behavior to such code to be "crazy". The simplest example I
    can think of is that unsigned int is big-endian, while unsigned long is little-endian, and I would even agree that such an implementation would
    be peculiar, but such an implementation could be fully conforming to the
    C standard.


    Would it be allowed (in the sense of being possible in a hypothetical
    but fully conforming implementation) to have "unsigned long" be 32-bit, without padding, while "unsigned int" is 64-bit wide with 32 value bits
    and 32 padding bits? A cpu might be able to handle 64-bit lumps faster
    than 32-bit lumps and choose such a setup to make "unsigned int" as fast
    as it can. (uint32_t in this case would be an alias for "unsigned
    long", as it can't have padding bits.)

    (I realise this is swapping your pink unicorn C implementation for a
    green unicorn C implementation, but sometimes it is fun to see how weird
    you can imagine while still being able to support conforming C.)

    If uint32_t and unsigned long have different sizes, it still might
    happen happen to "work", depending on calling conventions.  Passing a
    32-bit argument and telling printf to expect a 64-bit value clearly
    has undefined behavior, but perhaps both happen to be passed in 64-bit
    registers, for example.


    And that is sort of intimate knowledge of the ABI that I don't want to
    exploit, as already mentioned in my other post in this sub-thread.

    Which is precisely what's wrong about your approach - it relies upon intimate knowledge of the ABI. Specifically, it relies on unsigned int
    and unsigned long happening to have exactly the same size and representation.


    I don't think there is anything intrinsically wrong with writing code
    that makes assumptions about the target ABI - non-portable code has its essential place in programming. But there /is/ something wrong about
    making assumptions about an ABI while claiming you are writing portable
    code that does not make such assumptions. And there is something that
    is at least "stylistically questionable" about needlessly and wantonly
    doing so. By all means write code that relies on the specifics of the
    target or compiler, but do so knowingly, do so only when you have good
    reason for it, and do so in a way that is clear to anyone later trying
    to re-use the code on some other system.





    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael S@already5chosen@yahoo.com to comp.lang.c on Wed Jan 14 11:03:33 2026
    From Newsgroup: comp.lang.c

    On Tue, 13 Jan 2026 21:24:16 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:

    On 2026-01-11 08:32, Michael S wrote:
    On Sun, 11 Jan 2026 04:59:47 -0800
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:
    On Sat, 10 Jan 2026 22:02:03 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
    wrote:
    On 2026-01-09 07:18, Michael S wrote:
    On Thu, 8 Jan 2026 19:31:13 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
    wrote:
    ...
    I'd have no problem with your approach if you hadn't falsely
    claimed that "It is correct on all platforms".

    Which I didn't.

    On 2026-01-07 19:38, Michael S wrote:
    ...
    > No, it is correct on all implementation.

    The quote is taken out of context.
    The context was that on platforms that have properties (a) and (b)
    (see below) printing variables declared as uint32_t via %u is
    probably UB according to the Standard (I don't know for sure,
    however it is probable),

    I'm sure. uint32_t is an alias for some predefined integer type.

    This:
    uint32_t n = 42;
    printf("%u\n", n);
    has undefined behavior *unless* uint32_t happens to an alias for
    unsigned int in the current implementation -- not just any 32-bit
    unsigned integer type, only unsigned int.

    If uint32_t is an alias for unsigned long (which implies that
    unsigned long is exactly 32 bits), then the call's behavior is
    undefined. (It might happen to "work".)


    What exactly, assuming that conditions (a) and (b) fulfilled, should implementation do to prevent it from working?
    I mean short of completely crazy things that will make maintainer immediately fired?

    I'm quite positive that you would consider anything that might give unexpected behavior to such code to be "crazy". The simplest example
    I can think of is that unsigned int is big-endian, while unsigned
    long is little-endian, and I would even agree that such an
    implementation would be peculiar, but such an implementation could be
    fully conforming to the C standard.


    You are inventive!

    If uint32_t and unsigned long have different sizes, it still might
    happen happen to "work", depending on calling conventions.
    Passing a 32-bit argument and telling printf to expect a 64-bit
    value clearly has undefined behavior, but perhaps both happen to
    be passed in 64-bit registers, for example.


    And that is sort of intimate knowledge of the ABI that I don't want
    to exploit, as already mentioned in my other post in this
    sub-thread.

    Which is precisely what's wrong about your approach - it relies upon intimate knowledge of the ABI. Specifically, it relies on unsigned
    int and unsigned long happening to have exactly the same size and representation.


    I consider the latter a basic knowledge of ABI rather than an intimate.
    For me programming feels uncomfortable without such knowledge. That is,
    I can manage without, but do not want to.
    Your mileage appears to vary.





    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael S@already5chosen@yahoo.com to comp.lang.c on Wed Jan 14 11:10:38 2026
    From Newsgroup: comp.lang.c

    On Tue, 13 Jan 2026 22:17:09 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:

    On 2026-01-11 06:20, Michael S wrote:
    On Sat, 10 Jan 2026 22:02:03 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:

    On 2026-01-09 07:18, Michael S wrote:
    On Thu, 8 Jan 2026 19:31:13 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
    wrote:
    ...
    I'd have no problem with your approach if you hadn't falsely
    claimed that "It is correct on all platforms".

    Which I didn't.

    On 2026-01-07 19:38, Michael S wrote:
    ...
    No, it is correct on all implementation.


    The quote is taken out of context.
    The context was that on platforms that have properties (a) and (b)
    (see below) printing variables declared as uint32_t via %u is
    probably UB according to the Standard (I don't know for sure,
    however it is probable), but it can't cause troubles with
    production C compiler. Or with any C compiler that is made in
    intention of being used rather than crafted to prove theoretical
    points. Properties are:
    a) uint32_t aliased to 'unsigned long'
    b) 'unsigned int' is at least 32-bit wide.

    I never claimed that it is good idea on targets with 'unsigned int'
    that is narrower.

    I've looked for a previous restriction of this discussion to cases
    covered by a) and b) above. The closest I could find is the following:

    In the case I am talking about n declared as uint32_t.
    uint32_t is an alias of 'unsigned long' on 32-bit embedded targets,
    on 32-bit Linux, on 32-bit Windows and on 64-bit Windows. It is
    alias of 'unsigned int' on 64-bit Linux.

    Note several points: that is a period after the first use of
    "uint32_t", so "the case" you're specifying ends there. I read the
    next three lines as information about your working environment, not restrictions on the claimed validity of your preference for "%u" over
    "%lu". There is no mention of a restriction on the size of "unsigned
    int".



    Ignoring for a minute that what I claimed about 32-bit Linux is
    at best non-universal and at worst universally wrong, how would you
    formulate what I meant?
    My knowledge of English punctuation rules is rather minimal and even
    less than that for its US American variant.

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

    David Brown <david.brown@hesbynett.no> writes:
    [...]
    Would it be allowed (in the sense of being possible in a hypothetical
    but fully conforming implementation) to have "unsigned long" be
    32-bit, without padding, while "unsigned int" is 64-bit wide with 32
    value bits and 32 padding bits? A cpu might be able to handle 64-bit
    lumps faster than 32-bit lumps and choose such a setup to make
    "unsigned int" as fast as it can. (uint32_t in this case would be an
    alias for "unsigned long", as it can't have padding bits.)

    The *width* of an integer type is the number of value bits plus the sign
    bit, if any, so "64-bit wide" is an incorrect description.

    What would be possible is:

    - CHAR_BIT * sizeof (unsigned int) == 64
    - UINT_WIDTH == 32 (32 padding bits)
    - CHAR_BIT * sizeof (unsigned long) == 32
    - ULONG_WIDTH == 32 (no padding bits)

    The *_WIDTH macros are new in C23.

    [...]
    --
    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 James Kuyper@jameskuyper@alumni.caltech.edu to comp.lang.c on Wed Jan 14 22:19:34 2026
    From Newsgroup: comp.lang.c

    On 2026-01-14 04:03, Michael S wrote:
    On Tue, 13 Jan 2026 21:24:16 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
    ...
    I'm quite positive that you would consider anything that might give
    unexpected behavior to such code to be "crazy". The simplest example
    I can think of is that unsigned int is big-endian, while unsigned
    long is little-endian, and I would even agree that such an
    implementation would be peculiar, but such an implementation could be
    fully conforming to the C standard.


    You are inventive!

    As a programmer, I paid close attention to what was and was not
    guaranteed about the software that I used. As a result, I've noticed
    many things, such as the fact that the standard imposes no requirements
    on the order of the bytes (or even of the bits) that are used to
    represent arithmetic values.

    ...
    Which is precisely what's wrong about your approach - it relies upon
    intimate knowledge of the ABI. Specifically, it relies on unsigned
    int and unsigned long happening to have exactly the same size and
    representation.


    I consider the latter a basic knowledge of ABI rather than an intimate.
    For me programming feels uncomfortable without such knowledge. That is,
    I can manage without, but do not want to.
    Your mileage appears to vary.
    I've spent most of my career working under rules that explicitly
    prohibited me from writing code that depends upon such details. As a
    result, I actually have relatively little knowledge of how any
    particular implementation of C that I used decided to handle issues that
    the C standard left unspecified. I've always written my code so that it
    would do what it's supposed to do, regardless of which choices any given implementation made about things that are unspecified.

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

    On 14/01/2026 23:53, Keith Thompson wrote:
    David Brown <david.brown@hesbynett.no> writes:
    [...]
    Would it be allowed (in the sense of being possible in a hypothetical
    but fully conforming implementation) to have "unsigned long" be
    32-bit, without padding, while "unsigned int" is 64-bit wide with 32
    value bits and 32 padding bits? A cpu might be able to handle 64-bit
    lumps faster than 32-bit lumps and choose such a setup to make
    "unsigned int" as fast as it can. (uint32_t in this case would be an
    alias for "unsigned long", as it can't have padding bits.)

    The *width* of an integer type is the number of value bits plus the sign
    bit, if any, so "64-bit wide" is an incorrect description.


    Sorry, my terminology was imprecise. I had meant 32 bits wide (value
    bits) but a size of 64 bits (including padding).

    What would be possible is:

    - CHAR_BIT * sizeof (unsigned int) == 64
    - UINT_WIDTH == 32 (32 padding bits)
    - CHAR_BIT * sizeof (unsigned long) == 32
    - ULONG_WIDTH == 32 (no padding bits)

    The *_WIDTH macros are new in C23.

    [...]


    So you agree that it would be possible? (I'm sure we agree that it is
    very unlikely in practice!)

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael S@already5chosen@yahoo.com to comp.lang.c on Thu Jan 15 11:02:00 2026
    From Newsgroup: comp.lang.c

    On Wed, 14 Jan 2026 22:19:34 -0500
    James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
    On 2026-01-14 04:03, Michael S wrote:
    On Tue, 13 Jan 2026 21:24:16 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
    ...
    I'm quite positive that you would consider anything that might give
    unexpected behavior to such code to be "crazy". The simplest
    example I can think of is that unsigned int is big-endian, while
    unsigned long is little-endian, and I would even agree that such an
    implementation would be peculiar, but such an implementation could
    be fully conforming to the C standard.


    You are inventive!

    As a programmer, I paid close attention to what was and was not
    guaranteed about the software that I used. As a result, I've noticed
    many things, such as the fact that the standard imposes no
    requirements on the order of the bytes (or even of the bits) that are
    used to represent arithmetic values.

    Luckily apart from requirements of the Standard, compilers, except for toy/hobby ones, are constrained by need to have user. That places a
    bounds on creativity of their authors.
    ...
    Which is precisely what's wrong about your approach - it relies
    upon intimate knowledge of the ABI. Specifically, it relies on
    unsigned int and unsigned long happening to have exactly the same
    size and representation.


    I consider the latter a basic knowledge of ABI rather than an
    intimate. For me programming feels uncomfortable without such
    knowledge. That is, I can manage without, but do not want to.
    Your mileage appears to vary.
    I've spent most of my career working under rules that explicitly
    prohibited me from writing code that depends upon such details. As a
    result, I actually have relatively little knowledge of how any
    particular implementation of C that I used decided to handle issues
    that the C standard left unspecified. I've always written my code so
    that it would do what it's supposed to do, regardless of which
    choices any given implementation made about things that are
    unspecified.

    I don't have a lot of 1st hand experience of porting code between
    seriously diverse systems.
    From the people that have such experience I always hear the same thing: portability of program that was never actually ported is an illusion.
    Fine details of sort that we discuss in this subthread are rather small
    part of the reason behind it.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From James Kuyper@jameskuyper@alumni.caltech.edu to comp.lang.c on Thu Jan 15 06:10:32 2026
    From Newsgroup: comp.lang.c

    On 2026-01-14 04:10, Michael S wrote:
    On Tue, 13 Jan 2026 22:17:09 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:

    On 2026-01-11 06:20, Michael S wrote:
    On Sat, 10 Jan 2026 22:02:03 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:

    On 2026-01-09 07:18, Michael S wrote:
    On Thu, 8 Jan 2026 19:31:13 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
    wrote:
    ...
    I'd have no problem with your approach if you hadn't falsely
    claimed that "It is correct on all platforms".

    Which I didn't.

    On 2026-01-07 19:38, Michael S wrote:
    ...
    No, it is correct on all implementation.


    The quote is taken out of context.
    The context was that on platforms that have properties (a) and (b)
    (see below) printing variables declared as uint32_t via %u is
    probably UB according to the Standard (I don't know for sure,
    however it is probable), but it can't cause troubles with
    production C compiler. Or with any C compiler that is made in
    intention of being used rather than crafted to prove theoretical
    points. Properties are:
    a) uint32_t aliased to 'unsigned long'
    b) 'unsigned int' is at least 32-bit wide.

    I never claimed that it is good idea on targets with 'unsigned int'
    that is narrower.

    I've looked for a previous restriction of this discussion to cases
    covered by a) and b) above. The closest I could find is the following:

    In the case I am talking about n declared as uint32_t.
    uint32_t is an alias of 'unsigned long' on 32-bit embedded targets,
    on 32-bit Linux, on 32-bit Windows and on 64-bit Windows. It is
    alias of 'unsigned int' on 64-bit Linux.

    Note several points: that is a period after the first use of
    "uint32_t", so "the case" you're specifying ends there. I read the
    next three lines as information about your working environment, not
    restrictions on the claimed validity of your preference for "%u" over
    "%lu". There is no mention of a restriction on the size of "unsigned
    int".



    Ignoring for a minute that what I claimed about 32-bit Linux is
    at best non-universal and at worst universally wrong, how would you
    formulate what I meant?
    My knowledge of English punctuation rules is rather minimal and even
    less than that for its US American variant.

    I'm not sure exactly what you intended. And, as I mentioned in another sub-thread, I've worked for most of my career under rules that
    prohibited me from writing code that depends upon the kinds of details
    that you're talking about - as a result, I've had little reason to
    familiarize myself with those details. However, I can say that using
    "%u" to print a value of unsigned long type has no chance of working
    unless unsigned int and unsigned long have the same size and
    representation. Even if they do, the behavior is still undefined, but
    there's a pretty good chance it will work.
    How could it fail? As an extension, an implementation could define an
    ABI for use with variadic functions that adds a tag to each value
    indicating its type, and could add a feature to <stdarg.h> to access
    those tags. The printf() and scanf() families of functions could use
    that feature to check for compatibility between the type specified by
    the forma specifier, and the actual type of the corresponding argument.
    Upon finding a mismatch, it could issue run-time diagnostic or even
    abort your program. Such an implementation would be allowed by the fact
    the behavior of your program is undefined when there is such a mismatch.

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Thu Jan 15 04:00:57 2026
    From Newsgroup: comp.lang.c

    James Kuyper <jameskuyper@alumni.caltech.edu> writes:
    [...]
    I'm not sure exactly what you intended. And, as I mentioned in another sub-thread, I've worked for most of my career under rules that
    prohibited me from writing code that depends upon the kinds of details
    that you're talking about - as a result, I've had little reason to familiarize myself with those details. However, I can say that using
    "%u" to print a value of unsigned long type has no chance of working
    unless unsigned int and unsigned long have the same size and
    representation. Even if they do, the behavior is still undefined, but
    there's a pretty good chance it will work.
    [...]

    On one implementation (gcc, glibc, 64 bits), it *can* "work":

    ```
    #include <stdio.h>
    int main(void) {
    unsigned long x = 123456789;
    printf("sizeof (unsigned) = %zu\n", sizeof (unsigned));
    printf("sizeof (unsigned long) = %zu\n", sizeof (unsigned long));
    printf("x = %u\n", x);
    }
    ```

    The output on my system (after some compiler warnings):

    ```
    sizeof (unsigned) = 4
    sizeof (unsigned long) = 8
    x = 123456789
    ```

    Apparently printf tries to grab a 32-bit value and happens to get
    the low-order 32 bits of the 64-bit value that was passed. A value
    exceeding LONG_MAX is not printed correctly, but in principle it
    could be.

    Of course I do not advocate doing this other than as a test of an implementation's behavior.

    J.B.S. Haldane famously said that "The Universe is not only queerer
    than we suppose, but queerer than we can suppose." The same is
    true of undefined behavior.
    --
    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 James Kuyper@jameskuyper@alumni.caltech.edu to comp.lang.c on Thu Jan 15 20:08:06 2026
    From Newsgroup: comp.lang.c

    On 2026-01-15 07:00, Keith Thompson wrote:
    James Kuyper <jameskuyper@alumni.caltech.edu> writes:
    [...]
    I'm not sure exactly what you intended. And, as I mentioned in another
    sub-thread, I've worked for most of my career under rules that
    prohibited me from writing code that depends upon the kinds of details
    that you're talking about - as a result, I've had little reason to
    familiarize myself with those details. However, I can say that using
    "%u" to print a value of unsigned long type has no chance of working
    unless unsigned int and unsigned long have the same size and
    representation. Even if they do, the behavior is still undefined, but
    there's a pretty good chance it will work.
    [...]

    On one implementation (gcc, glibc, 64 bits), it *can* "work":

    ```
    #include <stdio.h>
    int main(void) {
    unsigned long x = 123456789;
    printf("sizeof (unsigned) = %zu\n", sizeof (unsigned));
    printf("sizeof (unsigned long) = %zu\n", sizeof (unsigned long));
    printf("x = %u\n", x);
    }
    ```

    The output on my system (after some compiler warnings):

    ```
    sizeof (unsigned) = 4
    sizeof (unsigned long) = 8
    x = 123456789
    ```

    Apparently printf tries to grab a 32-bit value and happens to get
    the low-order 32 bits of the 64-bit value that was passed. A value
    exceeding LONG_MAX is not printed correctly, but in principle it
    could be.

    I knew about that possibility, and had intended to word my comment to
    cover it, but I forgot. Thanks for covering it. The key point is that
    this only works for a large but limited range of values - it cannot work
    in general.

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Thu Jan 15 18:17:54 2026
    From Newsgroup: comp.lang.c

    James Kuyper <jameskuyper@alumni.caltech.edu> writes:
    On 2026-01-15 07:00, Keith Thompson wrote:
    James Kuyper <jameskuyper@alumni.caltech.edu> writes:
    [...]
    I'm not sure exactly what you intended. And, as I mentioned in another
    sub-thread, I've worked for most of my career under rules that
    prohibited me from writing code that depends upon the kinds of details
    that you're talking about - as a result, I've had little reason to
    familiarize myself with those details. However, I can say that using
    "%u" to print a value of unsigned long type has no chance of working
    unless unsigned int and unsigned long have the same size and
    representation. Even if they do, the behavior is still undefined, but
    there's a pretty good chance it will work.
    [...]

    On one implementation (gcc, glibc, 64 bits), it *can* "work":

    ```
    #include <stdio.h>
    int main(void) {
    unsigned long x = 123456789;
    printf("sizeof (unsigned) = %zu\n", sizeof (unsigned));
    printf("sizeof (unsigned long) = %zu\n", sizeof (unsigned long));
    printf("x = %u\n", x);
    }
    ```

    The output on my system (after some compiler warnings):

    ```
    sizeof (unsigned) = 4
    sizeof (unsigned long) = 8
    x = 123456789
    ```

    Apparently printf tries to grab a 32-bit value and happens to get
    the low-order 32 bits of the 64-bit value that was passed. A value
    exceeding LONG_MAX is not printed correctly, but in principle it
    could be.

    I knew about that possibility, and had intended to word my comment to
    cover it, but I forgot. Thanks for covering it. The key point is that
    this only works for a large but limited range of values - it cannot work
    in general.

    I suppose it depends on just what you mean by "in general".

    A conforming implementation *could* always print the mathematically
    correct value of a 64-bit unsigned long argument when printf is
    called with a 32-bit "%u" format. I could speculate about how
    this might work, but it doesn't really matter. Probably few if
    any implementations behave this way, but the nature of undefined
    behavior is that there is no behavior that violates the C standard.

    And of course the best advice is "don't do that".
    --
    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 Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Tue Feb 3 05:38:33 2026
    From Newsgroup: comp.lang.c

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

    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:

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

    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:


    I was responding to Scotty Lurndal's statement that the C
    standard was being paraphrased (by someone, it didn't matter to
    me who). I don't care about whether his statement is true; my
    interest is only in what part of the C standard he thinks is
    being paraphrased. He is in a position to answer that question,
    and more to the point he is the only person who is.

    It's pretty clear that the standard describes the printf
    function and the methods used to match the format characters
    to the data types of the arguments. The fact that James
    framed that as advice doesn't change interpretation of
    the text of the standard, whether or not you consider
    that to be a paraphrase.


    "The main rules for paraphrasing are to fully understand the
    original text, restate its core idea in your own words and
    sentence structure, use synonyms, and always cite the original
    source to avoid plagiarism, even if the wording is different.

    I see where the C standard says the macros in inttypes.h are
    suitable for use with printf (and scanf). That isn't at all
    the same as saying people should use them.

    Why on earth would the put them there if they didn't expect
    them to be used?

    Expecting they will be used in some cases is different than
    saying they should be used in all cases.
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Tue Feb 3 07:47:51 2026
    From Newsgroup: comp.lang.c

    Michael S <already5chosen@yahoo.com> writes:

    On Sun, 11 Jan 2026 11:51:43 -0800
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:

    On Sat, 10 Jan 2026 22:02:03 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:

    On 2026-01-09 07:18, Michael S wrote:

    On Thu, 8 Jan 2026 19:31:13 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
    wrote:

    ...

    I'd have no problem with your approach if you hadn't falsely
    claimed that "It is correct on all platforms".

    Which I didn't.

    On 2026-01-07 19:38, Michael S wrote:
    ...

    No, it is correct on all implementation.


    The quote is taken out of context.
    The context was that on platforms that have properties (a) and (b)
    (see below) printing variables declared as uint32_t via %u is
    probably UB according to the Standard (I don't know for sure,
    however it is probable), but it can't cause troubles with
    production C compiler. Or with any C compiler that is made in
    intention of being used rather than crafted to prove theoretical
    points. Properties are:
    a) uint32_t aliased to 'unsigned long'
    b) 'unsigned int' is at least 32-bit wide.

    It seems unlikely that any implementation would make such a
    choice. Can you name one that does?

    Four out of four target for which I write C programs for living in this decade:
    - Altera Nios2 (nios2-elf-gcc)
    - Arm Cortex-M bare metal (arm-none-eabi-gcc)
    - Win32-i386, various compilers
    - Win64-Amd64,various compilers

    Interesting. I wonder what factors motivated such a choice.

    Well, if I would be pedantic, then in this decade I also wrote several programs for Arm32 Linux, where I don't know whether uint32_t is alias
    of 'unsigned int' or 'unsigned long', few programs for AMD64 Linux,
    where I know that uint32_t is an alias of 'unsigned long' and may be one program for ARM64 Linux that is the same as AMD64 Linux.
    But all those outliers together constitute a tiny fraction of the code
    that I wrote recently.

    If variable 'u' is declared as uint32_t, a way to print it that is
    easy and also type-safe is

    printf( " u is %lu\n", u+0LU );
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Bart@bc@freeuk.com to comp.lang.c on Tue Feb 3 19:51:26 2026
    From Newsgroup: comp.lang.c

    On 03/02/2026 15:47, Tim Rentsch wrote:
    Michael S <already5chosen@yahoo.com> writes:

    On Sun, 11 Jan 2026 11:51:43 -0800
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:

    On Sat, 10 Jan 2026 22:02:03 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:

    On 2026-01-09 07:18, Michael S wrote:

    On Thu, 8 Jan 2026 19:31:13 -0500
    "James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
    wrote:

    ...

    I'd have no problem with your approach if you hadn't falsely
    claimed that "It is correct on all platforms".

    Which I didn't.

    On 2026-01-07 19:38, Michael S wrote:
    ...

    No, it is correct on all implementation.


    The quote is taken out of context.
    The context was that on platforms that have properties (a) and (b)
    (see below) printing variables declared as uint32_t via %u is
    probably UB according to the Standard (I don't know for sure,
    however it is probable), but it can't cause troubles with
    production C compiler. Or with any C compiler that is made in
    intention of being used rather than crafted to prove theoretical
    points. Properties are:
    a) uint32_t aliased to 'unsigned long'
    b) 'unsigned int' is at least 32-bit wide.

    It seems unlikely that any implementation would make such a
    choice. Can you name one that does?

    Four out of four target for which I write C programs for living in this
    decade:
    - Altera Nios2 (nios2-elf-gcc)
    - Arm Cortex-M bare metal (arm-none-eabi-gcc)
    - Win32-i386, various compilers
    - Win64-Amd64,various compilers

    Interesting. I wonder what factors motivated such a choice.

    Well, if I would be pedantic, then in this decade I also wrote several
    programs for Arm32 Linux, where I don't know whether uint32_t is alias
    of 'unsigned int' or 'unsigned long', few programs for AMD64 Linux,
    where I know that uint32_t is an alias of 'unsigned long' and may be one
    program for ARM64 Linux that is the same as AMD64 Linux.
    But all those outliers together constitute a tiny fraction of the code
    that I wrote recently.

    If variable 'u' is declared as uint32_t, a way to print it that is
    easy and also type-safe is

    printf( " u is %lu\n", u+0LU );

    What about a compound expression of several variables of mixed integer
    types, possibly even mixed with floats, some of whose types might either
    be conditional (depending on some macro), or opaque?
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Tue Feb 3 14:43:46 2026
    From Newsgroup: comp.lang.c

    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
    [...]
    If variable 'u' is declared as uint32_t, a way to print it that is
    easy and also type-safe is

    printf( " u is %lu\n", u+0LU );

    I prefer

    printf("u is %lu\n", (unsigned)long_u);

    I find it clearer.

    Anticipating your reply, this is my personal preference, my opinion,
    not backed up by any objective research.
    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Bart@bc@freeuk.com to comp.lang.c on Tue Feb 3 23:06:27 2026
    From Newsgroup: comp.lang.c

    On 03/02/2026 22:43, Keith Thompson wrote:
    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
    [...]
    If variable 'u' is declared as uint32_t, a way to print it that is
    easy and also type-safe is

    printf( " u is %lu\n", u+0LU );

    I prefer

    printf("u is %lu\n", (unsigned)long_u);

    I find it clearer.

    Is there a typo in there, or is the variable actually called 'long_u'?
    Then the message doesn't match.
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Tue Feb 3 15:33:44 2026
    From Newsgroup: comp.lang.c

    Bart <bc@freeuk.com> writes:
    On 03/02/2026 22:43, Keith Thompson wrote:
    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
    [...]
    If variable 'u' is declared as uint32_t, a way to print it that is
    easy and also type-safe is

    printf( " u is %lu\n", u+0LU );

    I prefer

    printf("u is %lu\n", (unsigned)long_u);

    I find it clearer.

    Is there a typo in there, or is the variable actually called 'long_u'?
    Then the message doesn't match.

    Yes, that was a typo, a misplaced ')' (not sure how the '_' got there).
    Thanks for catching it.

    The version I prefer (and I tested it this time) is:

    printf("u is %lu\n", (unsigned long)u);
    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Tue Feb 3 17:19:10 2026
    From Newsgroup: comp.lang.c

    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:

    David Brown <david.brown@hesbynett.no> writes:
    [...]

    C23 includes length specifiers with explicit bit counts, so "%w32u" is
    for an unsigned integer argument of 32 bits:

    """
    wN Specifies that a following b, B, d, i, o, u, x, or X conversion
    specifier applies to an integer argument with a specific width
    where N is a positive decimal integer with no leading zeros
    (the argument will have been promoted according to the integer
    promotions, but its value shall be converted to the unpromoted
    type); or that a following n conversion specifier applies to a
    pointer to an integer type argument with a width of N bits. All
    minimum-width integer types (7.22.1.2) and exact-width integer
    types (7.22.1.1) defined in the header <stdint.h> shall be
    supported. Other supported values of N are implementation-defined.
    """

    That looks to me that it would be a correct specifier for uint32_t,

    Yes, so for example this:

    uint32_t n = 42;
    printf("n = %w32u\n", n);

    is correct, if I'm reading it correctly. It's also correct for uint_least32_t, which is expected to be the same type as uint32_t
    if the latter exists. There's also support for the [u]int_fastN_t
    types, using for example "%wf32u" in place of "%w32u".

    and should also be fully defined behaviour for unsigned int and
    unsigned long if these are 32 bits wide.

    No, I don't think C23 says that.

    Right, it doesn't.

    If int and long happen to be the same
    width, they are still incompatible, and there is no printf format
    specifier that has defined behavior for both.

    That first sentence is a bit ambiguous

    wN Specifies that a following b, B, d, i, o, u, x, or X conversion
    specifier applies to an integer argument with a specific width ...

    but I don't think it means that it must accept *any* integer type
    of the specified width.

    As I read the standard there is no ambiguity. The first sentence
    says what the length modifier means. The second sentence says
    which types (if any) correspond to the description in the first
    sentence.
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Tue Feb 3 18:19:45 2026
    From Newsgroup: comp.lang.c

    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    David Brown <david.brown@hesbynett.no> writes:
    [...]

    C23 includes length specifiers with explicit bit counts, so "%w32u" is
    for an unsigned integer argument of 32 bits:

    """
    wN Specifies that a following b, B, d, i, o, u, x, or X conversion
    specifier applies to an integer argument with a specific width
    where N is a positive decimal integer with no leading zeros
    (the argument will have been promoted according to the integer
    promotions, but its value shall be converted to the unpromoted
    type); or that a following n conversion specifier applies to a
    pointer to an integer type argument with a width of N bits. All
    minimum-width integer types (7.22.1.2) and exact-width integer
    types (7.22.1.1) defined in the header <stdint.h> shall be
    supported. Other supported values of N are implementation-defined.
    """

    That looks to me that it would be a correct specifier for uint32_t,

    Yes, so for example this:

    uint32_t n = 42;
    printf("n = %w32u\n", n);

    is correct, if I'm reading it correctly. It's also correct for
    uint_least32_t, which is expected to be the same type as uint32_t
    if the latter exists. There's also support for the [u]int_fastN_t
    types, using for example "%wf32u" in place of "%w32u".

    and should also be fully defined behaviour for unsigned int and
    unsigned long if these are 32 bits wide.

    No, I don't think C23 says that.

    Right, it doesn't.

    If int and long happen to be the same
    width, they are still incompatible, and there is no printf format
    specifier that has defined behavior for both.

    That first sentence is a bit ambiguous

    wN Specifies that a following b, B, d, i, o, u, x, or X conversion
    specifier applies to an integer argument with a specific width ...

    but I don't think it means that it must accept *any* integer type
    of the specified width.

    As I read the standard there is no ambiguity. The first sentence
    says what the length modifier means. The second sentence says
    which types (if any) correspond to the description in the first
    sentence.

    The descriptions for all the other length modifiers name the types
    to which they apply in the first sentence. "hh" applies to signed
    char or unsigned char, "l" applies to long int or unsigned long int,
    "z" applies to size_t, and so forth. The first sentence of the
    description for "wN" says it "applies to an integer argument with
    a specific width".

    The intent is that "%w32d" applies to an argument of type
    int_least32_t or int32_t (if the latter exists, it must be the same
    type as the former).

    Suppose, hypothetically, that it had been the intent that "%w32d"
    applies to *any* signed integer type with a width of 32 bits (e.g.,
    both int and long if both are 32 bits wide). I think that the
    current wording could express that intent. The second sentence
    could taken as a clarification rather than a restriction. (An
    irrelevant aside: That might actually be a nice feature.)

    Assume an implementation with 32-bit int, 32-bit long, and 64-bit
    long long, where int32_t and int64_t are int and long long,
    respectively (e.g., "gcc -m32" with glibc on 64-bit Ubuntu), so
    none of the intN_t types are defined as long. Then this:

    printf("%w32d\n", 0L);

    has undefined behavior if we assume (as I do) that "%w32d" applies
    only to the type defined as int32_t (and int_least32_t). But the
    0L argument *is* "an integer argument with a specific width", and
    the following sentence "All minimum-width integer types (7.22.1.2)
    and exact-width integer types (7.22.1.1) defined in the header
    <stdint.h> shall be supported." does not contradict that.

    I think the phrase "an integer argument with a specific width" was
    an attempt to describe a specific set of types, but it was worded
    in a way that applies a larger set of types. I think the following
    sentence is not sufficiently clear in its attempt to restrict the
    list of applicable types.

    I understand the intent. Adding a format string that can apply
    to distinct incompatible types would be a major change that would
    surely be discussed in greater detail. But the current wording
    does not clearly express that intent, and one or more people here
    have, quite understandably, interpreted the wording in a way that's inconsistent with the presumed intent.

    The description of wfN is a bit clearer, but could also use some
    clarification that "a fastest minimum-width integer argument with
    a specific width" refers specifically to the [u]int_fastN_t types.

    I merely suggest a clarification, either a change in wording or
    a footnote.
    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Tue Feb 3 22:03:05 2026
    From Newsgroup: comp.lang.c

    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:

    Michael S <already5chosen@yahoo.com> writes:

    On Tue, 06 Jan 2026 16:29:04 -0800
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:

    On Tue, 6 Jan 2026 10:31:41 -0500
    James Kuyper <jameskuyper@alumni.caltech.edu> wrote:

    On 2026-01-06 04:29, Michael S wrote:

    On Tue, 6 Jan 2026 00:27:04 -0000 (UTC)
    Lawrence D?Oliveiro <ldo@nz.invalid> wrote:

    ...

    Section 7.8 of the C spec defines macros you can use so you
    don?t have to hard-code assumptions about the lengths of
    integers in printf-format strings.

    Did you ever try to use them? They look ugly.

    Which is more important, correctness or beauty?

    It depends.

    When I know for sure that incorrectness has no consequences, like
    in case of using %u to print 'unsigned long' on target with 32-bit
    longs, or like using %llu to print 'unsigned long' on target with
    64-bit longs, then beauty wins. Easily.

    Seriously?

    An example:

    unsigned long n = 42;
    printf("%u\n", n); // incorrect
    printf("%lu\n", n); // correct

    Are you really saying that the second version is so much uglier
    than the first that you'd rather write incorrect code?

    No, I don't think that it is much uglier. At worst, I think that
    correct version is tiny bit uglier. Not enough for beauty to win
    over "correctness", even when correctness is non-consequential.

    I hoped that you followed the sub-thread from the beginning and
    did not lost the context yet.

    The context to which I replied was you favoring beauty over
    correctness and using "%u" to print an unsigned long value as
    an example.

    I find it difficult to express how strongly I disagree.

    I think it would be more helpful to readers if you would put your
    efforts more into understanding and explaining why you disagree
    than into expressing how strongly you disagree.
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Wed Feb 4 06:22:33 2026
    From Newsgroup: comp.lang.c

    Bart <bc@freeuk.com> writes:

    On 03/02/2026 15:47, Tim Rentsch wrote:
    [...]
    If variable 'u' is declared as uint32_t, a way to print it that is
    easy and also type-safe is

    printf( " u is %lu\n", u+0LU );

    What about a compound expression of several variables of mixed
    integer types, possibly even mixed with floats, some of whose types
    might either be conditional (depending on some macro), or opaque?

    What is an example of a conditional/macro-dependent type? Also
    what sort of opaque types do you have in mind? What is the
    problem you want to solve here?
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Bart@bc@freeuk.com to comp.lang.c on Wed Feb 4 16:44:45 2026
    From Newsgroup: comp.lang.c

    On 04/02/2026 14:22, Tim Rentsch wrote:
    Bart <bc@freeuk.com> writes:

    On 03/02/2026 15:47, Tim Rentsch wrote:
    [...]
    If variable 'u' is declared as uint32_t, a way to print it that is
    easy and also type-safe is

    printf( " u is %lu\n", u+0LU );

    What about a compound expression of several variables of mixed
    integer types, possibly even mixed with floats, some of whose types
    might either be conditional (depending on some macro), or opaque?

    What is an example of a conditional/macro-dependent type?

    Example from SDL2:

    #if defined(_MSC_VER) && (_MSC_VER < 1600)
    ...
    #ifndef _UINTPTR_T_DEFINED
    #ifdef _WIN64
    typedef unsigned __int64 uintptr_t;
    #else
    typedef unsigned int uintptr_t;
    ...

    Example from SQLITE3:

    #ifdef SQLITE_OMIT_FLOATING_POINT
    # define double sqlite3_int64
    #endif


    Also what sort of opaque types do you have in mind?

    Things like time_t and clock_t, or the equivalent from libraries.

    Yes you could hunt down the exact underlying type (for clock_t in one
    case, it was under 6 layers of typedefs and macros), but that would be
    for a specific set of headers.

    For system headers, somebody could be using a header with different definitions. For user-libraries, it might be a slightly different version.


    What is the problem you want to solve here?

    The problem is that C expects an exact format-code when trying to use
    *printf functions, and for that you need to know the exact types of the expressions being passed. For example:

    uintptr_t x; // from above examples
    double y; //
    printf("x * y is %?", x * y); // What's '?'


    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Wed Feb 4 18:12:00 2026
    From Newsgroup: comp.lang.c

    On 04/02/2026 17:44, Bart wrote:
    On 04/02/2026 14:22, Tim Rentsch wrote:
    Bart <bc@freeuk.com> writes:

    On 03/02/2026 15:47, Tim Rentsch wrote:
    [...]
    If variable 'u' is declared as uint32_t, a way to print it that is
    easy and also type-safe is

          printf( " u is %lu\n", u+0LU );

    What about a compound expression of several variables of mixed
    integer types, possibly even mixed with floats, some of whose types
    might either be conditional (depending on some macro), or opaque?

    What is an example of a conditional/macro-dependent type?

    Example from SDL2:

      #if defined(_MSC_VER) && (_MSC_VER < 1600)
        ...
        #ifndef _UINTPTR_T_DEFINED
          #ifdef  _WIN64
            typedef unsigned __int64 uintptr_t;
          #else
            typedef unsigned int uintptr_t;
        ...

    Example from SQLITE3:

      #ifdef SQLITE_OMIT_FLOATING_POINT
      # define double sqlite3_int64
      #endif


    Also what sort of opaque types do you have in mind?

    Things like time_t and clock_t, or the equivalent from libraries.

    Yes you could hunt down the exact underlying type (for clock_t in one
    case, it was under 6 layers of typedefs and macros), but that would be
    for a specific set of headers.

    For system headers, somebody could be using a header with different definitions. For user-libraries, it might be a slightly different version.


     What is the problem you want to solve here?

    The problem is that C expects an exact format-code when trying to use *printf functions, and for that you need to know the exact types of the expressions being passed. For example:

      uintptr_t x;                    // from above examples
      double y;                       //
      printf("x * y is %?", x * y);   // What's '?'



    So are you asking because you don't know what Tim's construction does
    with these types, or because you want to know if there is a portable and
    safe way to print out any arithmetic type, or because you are perfectly
    aware that C's printf has limitations and you want to post about how
    terrible C is and how great your own language is?

    The point of both Tim's and Keith's solutions is that you do /not/ need
    to know the exact type of the expression you are printing - C's
    conversion rules let them work with a range of different original types.
    They were both picked specifically for the case of "uint32_t", but can handle more types. Tim's can be used for any integer type of rank up to "unsigned long int" (and thus not "long long" types), while Keith's will
    be fine with any integer type and any floating point type as long as the
    value of the integer part of the floating point value is within the
    range of "unsigned long int".



    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Janis Papanagnou@janis_papanagnou+ng@hotmail.com to comp.lang.c on Wed Feb 4 18:48:10 2026
    From Newsgroup: comp.lang.c

    On 2026-02-04 18:12, David Brown wrote:
    On 04/02/2026 17:44, Bart wrote:
    On 04/02/2026 14:22, Tim Rentsch wrote:
     What is the problem you want to solve here?

    The problem is that C expects an exact format-code when trying to use
    *printf functions, and for that you need to know the exact types of
    the expressions being passed. [...]

    [...], or because you are perfectly
    aware that C's printf has limitations and you want to post about how terrible C is and how great your own language is?

    That was pretty obvious [to me] since I seem to recall that just
    recently he had posted an example of his language with a generic
    formatter, IIRC.

    Of course he has a point in criticizing this old 'printf' design;
    providing a well typed argument but needing to "find" the right
    formatting specifier anyway. Crude, but that's "C". And rarely
    anyone will be interested in discussions about this old inherent
    "C" design. Likewise about discussions of his "own language(s)".

    Janis

    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Bart@bc@freeuk.com to comp.lang.c on Wed Feb 4 18:11:34 2026
    From Newsgroup: comp.lang.c

    On 04/02/2026 17:12, David Brown wrote:
    On 04/02/2026 17:44, Bart wrote:
    On 04/02/2026 14:22, Tim Rentsch wrote:
    Bart <bc@freeuk.com> writes:

    On 03/02/2026 15:47, Tim Rentsch wrote:
    [...]
    If variable 'u' is declared as uint32_t, a way to print it that is
    easy and also type-safe is

          printf( " u is %lu\n", u+0LU );

    What about a compound expression of several variables of mixed
    integer types, possibly even mixed with floats, some of whose types
    might either be conditional (depending on some macro), or opaque?

    What is an example of a conditional/macro-dependent type?

    Example from SDL2:

       #if defined(_MSC_VER) && (_MSC_VER < 1600)
         ...
         #ifndef _UINTPTR_T_DEFINED
           #ifdef  _WIN64
             typedef unsigned __int64 uintptr_t;
           #else
             typedef unsigned int uintptr_t;
         ...

    Example from SQLITE3:

       #ifdef SQLITE_OMIT_FLOATING_POINT
       # define double sqlite3_int64
       #endif


    Also what sort of opaque types do you have in mind?

    Things like time_t and clock_t, or the equivalent from libraries.

    Yes you could hunt down the exact underlying type (for clock_t in one
    case, it was under 6 layers of typedefs and macros), but that would be
    for a specific set of headers.

    For system headers, somebody could be using a header with different
    definitions. For user-libraries, it might be a slightly different
    version.


     What is the problem you want to solve here?

    The problem is that C expects an exact format-code when trying to use
    *printf functions, and for that you need to know the exact types of
    the expressions being passed. For example:

       uintptr_t x;                    // from above examples
       double y;                       //
       printf("x * y is %?", x * y);   // What's '?'



    So are you asking because you don't know what Tim's construction does
    with these types, or because you want to know if there is a portable and safe way to print out any arithmetic type, or because you are perfectly aware that C's printf has limitations and you want to post about how terrible C is and how great your own language is?


    I was reponding to the example of a single variable with ONE type, that happens to be uint32_t, apparently a standard C type.

    Yes maybe that particular strategy might work (you know it is an integer
    and that it is unsigned).

    But it doesn't solve the general problem: even if there is a single type involved, it might be conditional or opaque (or its type is changed
    required all format codes to be revised.

    Or there is an expression of mixed types.

    and you want to post about how
    terrible C is and how great your own language is?

    I think pretty much every language except C seems to have solved this.


    The point of both Tim's and Keith's solutions is that you do /not/ need
    to know the exact type of the expression you are printing - C's
    conversion rules let them work with a range of different original types.

    OK.

     They were both picked specifically for the case of "uint32_t", but can handle more types.  Tim's can be used for any integer type of rank up to "unsigned long int" (and thus not "long long" types),

    So not such a great range.

    while Keith's will
    be fine with any integer type and any floating point type as long as the value of the integer part of the floating point value is within the
    range of "unsigned long int".

    Better, *if* you know the expression has an unsigned integer type.

    So as far as I'm concerned, the general problem remains. There are only workarounds and special cases that every user has to work out for
    themselves.

    Meanwhile C11 (_Generic) and C23 ("%w" formats) don't appear to have
    made much impact. It's not fixing it at the right level. But at least
    you can now have a 999-bit type that you probably can't print even if
    you wrote "%w999d"; or can you?
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Wed Feb 4 21:09:22 2026
    From Newsgroup: comp.lang.c

    On 04/02/2026 19:11, Bart wrote:
    On 04/02/2026 17:12, David Brown wrote:
    On 04/02/2026 17:44, Bart wrote:
    On 04/02/2026 14:22, Tim Rentsch wrote:
    Bart <bc@freeuk.com> writes:

    On 03/02/2026 15:47, Tim Rentsch wrote:
    [...]
    If variable 'u' is declared as uint32_t, a way to print it that is >>>>>> easy and also type-safe is

          printf( " u is %lu\n", u+0LU );

    What about a compound expression of several variables of mixed
    integer types, possibly even mixed with floats, some of whose types
    might either be conditional (depending on some macro), or opaque?

    What is an example of a conditional/macro-dependent type?

    Example from SDL2:

       #if defined(_MSC_VER) && (_MSC_VER < 1600)
         ...
         #ifndef _UINTPTR_T_DEFINED
           #ifdef  _WIN64
             typedef unsigned __int64 uintptr_t;
           #else
             typedef unsigned int uintptr_t;
         ...

    Example from SQLITE3:

       #ifdef SQLITE_OMIT_FLOATING_POINT
       # define double sqlite3_int64
       #endif


    Also what sort of opaque types do you have in mind?

    Things like time_t and clock_t, or the equivalent from libraries.

    Yes you could hunt down the exact underlying type (for clock_t in one
    case, it was under 6 layers of typedefs and macros), but that would
    be for a specific set of headers.

    For system headers, somebody could be using a header with different
    definitions. For user-libraries, it might be a slightly different
    version.


     What is the problem you want to solve here?

    The problem is that C expects an exact format-code when trying to use
    *printf functions, and for that you need to know the exact types of
    the expressions being passed. For example:

       uintptr_t x;                    // from above examples
       double y;                       //
       printf("x * y is %?", x * y);   // What's '?'



    So are you asking because you don't know what Tim's construction does
    with these types, or because you want to know if there is a portable
    and safe way to print out any arithmetic type, or because you are
    perfectly aware that C's printf has limitations and you want to post
    about how terrible C is and how great your own language is?


    I was reponding to the example of a single variable with ONE type, that happens to be uint32_t, apparently a standard C type.


    You know perfectly well that "uint32_t" is not a standard type - it is a typedef for a standard or extended integer type.

    And you know perfectly well that the constructions here from Tim and
    Keith demonstrate safe ways to print values of type "uint32_t",
    regardless of whether it is a typedef for "unsigned int", "unsigned long
    int", or an extend integer type. That was the point of their posts.

    Yes maybe that particular strategy might work (you know it is an integer
    and that it is unsigned).

    What did you think an "uint32_t" was, if not a type of unsigned integer?
    And there is no "maybe" about it - the strategies work.

    If you have other arithmetic types, then you need to adapt the strategy
    to fit - you need something that covers the ranges of the data you are
    dealing with.


    But it doesn't solve the general problem: even if there is a single type involved, it might be conditional or opaque (or its type is changed
    required all format codes to be revised.

    Or there is an expression of mixed types.


    There is no such thing as an "expression of mixed types". There are expressions formed with operators applied to subexpressions of different
    types - the rules of C state very clearly how those subexpressions are converted. (For most binary operators, these are the "usual arithmetic conversions".) You know this too.

    and you want to post about how
    terrible C is and how great your own language is?

    I think pretty much every language except C seems to have solved this.


    No, not all - but certainly many languages have more convenient handling
    of printing expressions. C's method works - it has done its job for
    half a century - but no one will argue that it is a bit clumsy. And if
    you are rough or lazy about it, it can be unsafe. If it bothers you too
    much, you can make a reasonable enough type-safe print facility with
    _Generic and variadic macros.


    The point of both Tim's and Keith's solutions is that you do /not/
    need to know the exact type of the expression you are printing - C's
    conversion rules let them work with a range of different original types.

    OK.

      They were both picked specifically for the case of "uint32_t", but
    can handle more types.  Tim's can be used for any integer type of rank
    up to "unsigned long int" (and thus not "long long" types),

    So not such a great range.

    The used "unsigned long" and 0UL precisely because the range was great
    enough for the purpose at hand. Changing those to "%llu", "unsigned
    long long" and "0ULL" is an obvious way to cover all unsigned integer
    types. Changing it to "%g", "double" and "0.0" covers all integer types
    and floats and doubles. (Supporting long doubles is left as an exercise
    for the reader.)


     while Keith's will
    be fine with any integer type and any floating point type as long as
    the value of the integer part of the floating point value is within
    the range of "unsigned long int".

    Better, *if* you know the expression has an unsigned integer type.


    Keith's method will also work as long as the type can be converted to an "unsigned long" - that includes floating point values as long as they
    are in range. Of course if he expected to print out floating point
    types, he'd use an appropriate floating point format.

    So as far as I'm concerned, the general problem remains. There are only workarounds and special cases that every user has to work out for themselves.


    If you can't figure this out, buy a "C programming for dummies" book and
    start at the beginning. Yes, printf formatting can be ugly and
    inconvenient compared to a number of other languages, but it is hardly
    rocket science.

    Meanwhile C11 (_Generic) and C23 ("%w" formats) don't appear to have
    made much impact. It's not fixing it at the right level. But at least
    you can now have a 999-bit type that you probably can't print even if
    you wrote "%w999d"; or can you?

    C23 formats haven't made an impact because C23 library support is only
    now beginning to appear. _Generic is used by people who know how to
    program with C11 and find _Generic useful. In practice it is quite
    rarely needed, since generally C programmers know what types they are
    using, and often a normal macro is all you need. But I've used _Generic occasionally myself.

    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Bart@bc@freeuk.com to comp.lang.c on Wed Feb 4 20:42:56 2026
    From Newsgroup: comp.lang.c

    On 04/02/2026 20:09, David Brown wrote:
    On 04/02/2026 19:11, Bart wrote:
    On 04/02/2026 17:12, David Brown wrote:

    So are you asking because you don't know what Tim's construction does
    with these types, or because you want to know if there is a portable
    and safe way to print out any arithmetic type, or because you are
    perfectly aware that C's printf has limitations and you want to post
    about how terrible C is and how great your own language is?


    I was reponding to the example of a single variable with ONE type,
    that happens to be uint32_t, apparently a standard C type.


    You know perfectly well that "uint32_t" is not a standard type - it is a typedef for a standard or extended integer type.

    And you know perfectly well that the constructions here from Tim and
    Keith demonstrate safe ways to print values of type "uint32_t",
    regardless of whether it is a typedef for "unsigned int", "unsigned long int", or an extend integer type.  That was the point of their posts.

    And one of mine is that you might not know the type is 'uint32_t'.

    Even if you were 100% sure, an update might change it, and the format
    might no longer be appropriate. (Eg. it might become signed, but gcc
    will not report that, at least not with Wall + Wextra + Wpedantic.)

    Yes maybe that particular strategy might work (you know it is an
    integer and that it is unsigned).

    What did you think an "uint32_t" was, if not a type of unsigned integer?
     And there is no "maybe" about it - the strategies work.

    See above.


    If you have other arithmetic types, then you need to adapt the strategy
    to fit - you need something that covers the ranges of the data you are dealing with.


    But it doesn't solve the general problem: even if there is a single
    type involved, it might be conditional or opaque (or its type is
    changed required all format codes to be revised.

    Or there is an expression of mixed types.


    There is no such thing as an "expression of mixed types".  There are expressions formed with operators applied to subexpressions of different types - the rules of C state very clearly how those subexpressions are converted.  (For most binary operators, these are the "usual arithmetic conversions".)  You know this too.

    An expression of mixed types means one that involves a number of
    different types amongst its types.

    Sure, the rules will tell you what the result will be, but you have to
    work it out, and to do that, you have to know what each of those types
    are (again, see above).

    Try this one for example; T, U and V are three numeric types exported by version 2.1 of some library:

    T x;
    U y;
    V z;
    printf("%?", x + y * z);

    You can spend some time hunting down those types and figuring out the
    result type (either one of T U V or maybe W). But how confident will you
    be that it will still work on 2.2?

    The change may be subtle enough that no warning is given, but enough to
    give a wrong result.


    and you want to post about how
    terrible C is and how great your own language is?

    I think pretty much every language except C seems to have solved this.


    No, not all - but certainly many languages have more convenient handling
    of printing expressions.  C's method works - it has done its job for
    half a century - but no one will argue that it is a bit clumsy.  And if
    you are rough or lazy about it, it can be unsafe.  If it bothers you too much, you can make a reasonable enough type-safe print facility with _Generic and variadic macros.

    So, a workaround that every user has to bother with. That's a bad sign.


    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Michael S@already5chosen@yahoo.com to comp.lang.c on Wed Feb 4 22:48:10 2026
    From Newsgroup: comp.lang.c

    On Wed, 4 Feb 2026 21:09:22 +0100
    David Brown <david.brown@hesbynett.no> wrote:


    Changing it to "%g", "double" and "0.0"
    covers all integer types and floats and doubles. (Supporting long
    doubles is left as an exercise for the reader.)


    'double' is insufficient for integers with magnitude above 2**53.
    That is, it will print something that is not complete nonsense, but not
    an exact number that you wanted.



    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Michael S@already5chosen@yahoo.com to comp.lang.c on Wed Feb 4 22:57:25 2026
    From Newsgroup: comp.lang.c

    On Wed, 4 Feb 2026 18:11:34 +0000
    Bart <bc@freeuk.com> wrote:

    On 04/02/2026 17:12, David Brown wrote:

    and you want to post about how
    terrible C is and how great your own language is?

    I think pretty much every language except C seems to have solved this.



    How do you do it in Fortran?
    Also, there are many languages that "solved" it at very high cost of primitivity of their formatting features. E.g. Pascal.
    I don't remember where Ada stands in this picturee. In case of Ada95 or
    newer, more like don't know rather then don't remember.


    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Bart@bc@freeuk.com to comp.lang.c on Wed Feb 4 23:24:49 2026
    From Newsgroup: comp.lang.c

    On 04/02/2026 20:57, Michael S wrote:
    On Wed, 4 Feb 2026 18:11:34 +0000
    Bart <bc@freeuk.com> wrote:

    On 04/02/2026 17:12, David Brown wrote:

    > and you want to post about how
    > terrible C is and how great your own language is?

    I think pretty much every language except C seems to have solved this.



    How do you do it in Fortran?
    Also, there are many languages that "solved" it at very high cost of primitivity of their formatting features. E.g. Pascal.
    I don't remember where Ada stands in this picturee. In case of Ada95 or newer, more like don't know rather then don't remember.


    Printing expressions involves two kinds of info other than their values:

    (1) The types of the values being printed
    (2) The desired layout and appearance

    (1) Is always known by the language/compiler/interpreter
    (2) Is optional when sensible defaults are used

    C's printf scheme always needs both (1) and (2).

    Some languages that have adopted C's printf scheme still need (1), but
    two I've just tried (Go and OCaml) will report a runtime error if there
    is a mismatch.

    FORTRAN IV probably had more primitive I/O than C. Ada is also
    long-winded: you need to call some type-specific function, but it will
    at least be on top of the types too.

    Some languages get it right, so that (1) is not needed. Examples from
    old languages include the Algols, Pascal and BASIC. This is where you
    just do the equivalent of:

    print x

    in whatever syntax is required. A selection of modern languages where
    you don't need that (1) type info is:

    Lua, Python, Julia, Rust, Nim, Odin, JavaScript, Zig (types optional)

    With C, I don't have a particular problem with the layout features,
    other than you /always/ have to provide a format string even with
    throwaway code.

    The problem is working out and maintaining the format codes, and not
    having compile-time checks unless you use a big, slow compiler with the correct warnings enabled.

    Some languages are worse however, despite not needing type codes; this
    is Zig, although usually, you'd use an alias for the first part:

    @import("std").debug.print("{} {}\n", .{i, x});

    The C equivalent is this (when i is i32 and x is f64):

    printf("%d %d\n", i, x);

    Suddenly C doesn't look so bad!

    (I just noticed that the second %d should have been %f, but decided to
    leave it: THIS is the problem.)
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Wed Feb 4 15:39:46 2026
    From Newsgroup: comp.lang.c

    Bart <bc@freeuk.com> writes:
    [...]
    The problem is that C expects an exact format-code when trying to use
    *printf functions, and for that you need to know the exact types of
    the expressions being passed. For example:

    uintptr_t x; // from above examples
    double y; //
    printf("x * y is %?", x * y); // What's '?'

    Since you asked...

    '?' is 'f' (or 'g' or 'e', or 'a', or any of those in upper case).
    `x * y` is of type double.

    When an integer operand is multiplied by (or added to, or ...) a
    floating-point operand, the integer operand is converted to the type
    of the floating-point operand. The "usual arithmetic conversions"
    are moderately complicated, but that particular rule is simple
    enough that I didn't have to look it up (though I did double-check
    it, no pun intended).

    If I didn't know that, I'd look it up. If I wanted something other
    than the usual arithmetic conversions, I'd force the result I want
    using a cast or casts. And I know it's just an example, but in
    real life I'd spend some time thinking about *why* I'm multiplying
    a uintptr_t by a double (I can't think of a realistic scenario where
    that would be appropriate), and likely concluding that one or both of
    the operands should have been of some other type in the first place.

    Yes, printf requires an exact type for each argument. Yes, that
    can be inconvenient. But updating printf so it can take arguments
    of any type and know what to do with them would require changes
    to the language that nobody, as far as I know, has proposed.
    (Any such proposal that would work only for the *printf functions,
    not for arbitrary user-written code, would probably not be accepted.)
    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Bart@bc@freeuk.com to comp.lang.c on Wed Feb 4 23:52:12 2026
    From Newsgroup: comp.lang.c

    On 04/02/2026 23:39, Keith Thompson wrote:
    Bart <bc@freeuk.com> writes:
    [...]
    The problem is that C expects an exact format-code when trying to use
    *printf functions, and for that you need to know the exact types of
    the expressions being passed. For example:

    uintptr_t x; // from above examples
    double y; //
    printf("x * y is %?", x * y); // What's '?'

    Since you asked...

    '?' is 'f' (or 'g' or 'e', or 'a', or any of those in upper case).
    `x * y` is of type double.

    The 'from above examples' applies to both x and y. That means that
    'double' /may/ have been redefined like this (from my post):

    #ifdef SQLITE_OMIT_FLOATING_POINT
    # define double sqlite3_int64
    #endif

    I don't know what 'sqlite3_int64' is, but it sounds like a signed integer.

    I was asked to give examples of conditional types, and thought it best
    to do so from actual programs.
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Wed Feb 4 16:04:06 2026
    From Newsgroup: comp.lang.c

    Bart <bc@freeuk.com> writes:
    [...]
    Meanwhile C11 (_Generic) and C23 ("%w" formats) don't appear to have
    made much impact. It's not fixing it at the right level. But at least
    you can now have a 999-bit type that you probably can't print even if
    you wrote "%w999d"; or can you?

    No, the "%wN" and "%wfN" length modifiers cannot (reliably) be
    used with bit-precise integer types. The standard requires them to
    support the widths of the types defined in <stdint.h>, typically 8,
    16, 32, and 64 bits. The standard says that "Other supported values
    of N are implementation-defined", but any bit-precise integer type
    is incompatible with any of the <stdint.h> types.

    In a quick look, I don't see any standard ways to print (or read)
    values of bit-precise integer types, either in N3220 (C23 draft)
    or in N2783 (latest working draft for C202y). I find this suprising
    and disappointing.
    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Thu Feb 5 12:41:26 2026
    From Newsgroup: comp.lang.c

    On 04/02/2026 21:42, Bart wrote:
    On 04/02/2026 20:09, David Brown wrote:
    On 04/02/2026 19:11, Bart wrote:
    On 04/02/2026 17:12, David Brown wrote:

    So are you asking because you don't know what Tim's construction
    does with these types, or because you want to know if there is a
    portable and safe way to print out any arithmetic type, or because
    you are perfectly aware that C's printf has limitations and you want
    to post about how terrible C is and how great your own language is?


    I was reponding to the example of a single variable with ONE type,
    that happens to be uint32_t, apparently a standard C type.


    You know perfectly well that "uint32_t" is not a standard type - it is
    a typedef for a standard or extended integer type.

    And you know perfectly well that the constructions here from Tim and
    Keith demonstrate safe ways to print values of type "uint32_t",
    regardless of whether it is a typedef for "unsigned int", "unsigned
    long int", or an extend integer type.  That was the point of their posts.

    And one of mine is that you might not know the type is 'uint32_t'.


    Just as long as we are clear that Tim and Keith's constructs were for
    anything other than "uint32_t", whatever its underlying type may be.
    For other selections of types, other similar constructs are needed, as appropriate.

    Even if you were 100% sure, an update might change it, and the format
    might no longer be appropriate. (Eg. it might become signed, but gcc
    will not report that, at least not with Wall + Wextra + Wpedantic.)

    Again, you are talking about types other than uint32_t? Although the underlying type used for "uint32_t" is not known, its characteristics
    are, including that it is unsigned.

    Usually when you have a type T in your code, you know some things about
    it - you typically know if it is arithmetic, integer, floating point,
    you know something about its range. How much you know will vary, but a
    type you know absolutely nothing about is unlikely to be of any use in code.


    Yes maybe that particular strategy might work (you know it is an
    integer and that it is unsigned).

    What did you think an "uint32_t" was, if not a type of unsigned
    integer?   And there is no "maybe" about it - the strategies work.

    See above.


    If you have other arithmetic types, then you need to adapt the
    strategy to fit - you need something that covers the ranges of the
    data you are dealing with.


    But it doesn't solve the general problem: even if there is a single
    type involved, it might be conditional or opaque (or its type is
    changed required all format codes to be revised.

    Or there is an expression of mixed types.


    There is no such thing as an "expression of mixed types".  There are
    expressions formed with operators applied to subexpressions of
    different types - the rules of C state very clearly how those
    subexpressions are converted.  (For most binary operators, these are
    the "usual arithmetic conversions".)  You know this too.

    An expression of mixed types means one that involves a number of
    different types amongst its types.


    Okay, that's what /you/ mean by that phrase. It is not an accurate description - in any statically typed language, an expression will have
    a single type. Subexpressions can be different types. But while I do
    not approve of your terms here, I do understand what you are talking about.

    Sure, the rules will tell you what the result will be, but you have to
    work it out, and to do that, you have to know what each of those types
    are (again, see above).

    No, the /compiler/ has to work it out. Whether /you/ need to work it
    out or not, depends on what you are doing with the result.

    If you have "T x;", and you write "(unsigned long) x" (as Keith
    suggested), then you know the type of that expression - without knowing
    the type of T. You need to know that "T" is a type that can be
    converted to "unsigned long" (any arithmetic or pointer type will do),
    and you need to know that the value of "x" is suitable for the
    conversion to be defined (so if "x" is floating point, it needs to be in range). If you don't know at least that much about "x", you probably
    should not be writing code with it.

    If you write "x + 0UL" (as Tim suggested), you know the resulting type
    if "T" is an integer type. If T is a floating point type, however, the resulting expression will have that floating point type. And if it is a pointer type, the result will have that pointer type. On the other
    hand, you don't have any restrictions in the values of "x".

    Do you need to know the /exact/ type of an expression? Sometimes yes, sometimes no. Since we are talking about ways to print out values
    without knowing their exact types, clearly we don't need to know the
    exact type here. We need to know certain characteristics, but not all details. This is very common in coding.


    Try this one for example; T, U and V are three numeric types exported by version 2.1 of some library:

       T x;
       U y;
       V z;
       printf("%?", x + y * z);

    You can spend some time hunting down those types and figuring out the
    result type (either one of T U V or maybe W). But how confident will you
    be that it will still work on 2.2?


    You need to know enough about the types to know how to use them for the
    things you want to do with them. In the real world, the names of the
    types usually makes the basics clear. For a library, the documentation
    will make it clear. So for example, the "time_t" type in the C language
    is documented for use in functions like "mktime" and "gmtime" - but not
    for printing out directly with printf. You are given that it is a "real
    type" - so you can convert it to a double and printf that, or you can
    use functions like "gmtime" and print out the elements of the struct tm.

    C is a statically typed language. In a statically typed language, you
    cannot have a function that can handle arbitrary types. Languages that provide a to print arbitrary types do so using methods that are not
    supported by C - templates, overloads, type methods or helper functions
    to convert different types to a common string format, etc.

    The change may be subtle enough that no warning is given, but enough to
    give a wrong result.

    So make sure you know what you are doing. Know /enough/ about the types
    you are using, and how you can safely use them.

    Ultimately, you can't protect against all sources of idiocy or malice.
    If a library has "typedef long int integer;" and later versions have
    "typedef _Complex double integer;", then the library is at fault.



    and you want to post about how
    terrible C is and how great your own language is?

    I think pretty much every language except C seems to have solved this.


    No, not all - but certainly many languages have more convenient
    handling of printing expressions.  C's method works - it has done its
    job for half a century - but no one will argue that it is a bit
    clumsy.  And if you are rough or lazy about it, it can be unsafe.  If
    it bothers you too much, you can make a reasonable enough type-safe
    print facility with _Generic and variadic macros.

    So, a workaround that every user has to bother with. That's a bad sign.


    How many people do you know who have actually written and use a C11
    print system using _Generic and variadic macros? I don't know any.
    (I've written simple examples as proofs of concept, posted in this
    group, but not for real use.) It turns out that people /don't/ have to
    have workarounds. "printf" has its limitations - there's no doubt
    there. But it is good enough for most people and most uses.


    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Thu Feb 5 12:51:53 2026
    From Newsgroup: comp.lang.c

    On 05/02/2026 00:52, Bart wrote:
    On 04/02/2026 23:39, Keith Thompson wrote:
    Bart <bc@freeuk.com> writes:
    [...]
    The problem is that C expects an exact format-code when trying to use
    *printf functions, and for that you need to know the exact types of
    the expressions being passed. For example:

       uintptr_t x;                    // from above examples
       double y;                       //
       printf("x * y is %?", x * y);   // What's '?'

    Since you asked...

    '?' is 'f' (or 'g' or 'e', or 'a', or any of those in upper case).
    `x * y` is of type double.

    The 'from above examples' applies to both x and y. That means that
    'double' /may/ have been redefined like this (from my post):

      #ifdef SQLITE_OMIT_FLOATING_POINT
      # define double sqlite3_int64
      #endif

    I don't know what 'sqlite3_int64' is, but it sounds like a signed integer.

    I was asked to give examples of conditional types, and thought it best
    to do so from actual programs.

    What you have found is an idiocy in SQLITE, not a problem in the C
    language or printf. If the macro "SQLITE_OMIT_FLOATING_POINT" is
    defined, then the type named "sqlite3_int64" is not an integer type, nor
    can it hold arbitrary 64-bit integers (as Michael S pointed out, and I
    assume accurately, it can hold 53 bit integers). I do not know what
    this type is used for in the code, but something like "sqlite3_bignum"
    would be a far better choice of name. And if it is intended that people
    print out these values directly, defining "PRsqlite3_bignum" to "%g" or
    "%llu" as appropriate would be helpful. (Yes, the resulting printf
    statements would be ugly - better ugly and correct than wrong).

    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Bart@bc@freeuk.com to comp.lang.c on Thu Feb 5 17:42:04 2026
    From Newsgroup: comp.lang.c

    On 05/02/2026 11:41, David Brown wrote:
    On 04/02/2026 21:42, Bart wrote:

    Usually when you have a type T in your code, you know some things about
    it - you typically know if it is arithmetic, integer, floating point,
    you know something about its range.

    How about time_t, clock_t, off_t?

      How much you know will vary, but a
    type you know absolutely nothing about is unlikely to be of any use in
    code.

    The problem is that that format code is tied to the type of the
    expression. That means that as your program evolves and the types
    change, or the expression changes (so another term's type becomes
    dominant), then you have to check all such format codes.


    Yes maybe that particular strategy might work (you know it is an
    integer and that it is unsigned).

    What did you think an "uint32_t" was, if not a type of unsigned
    integer?   And there is no "maybe" about it - the strategies work.

    See above.


    If you have other arithmetic types, then you need to adapt the
    strategy to fit - you need something that covers the ranges of the
    data you are dealing with.


    But it doesn't solve the general problem: even if there is a single
    type involved, it might be conditional or opaque (or its type is
    changed required all format codes to be revised.

    Or there is an expression of mixed types.


    There is no such thing as an "expression of mixed types".  There are
    expressions formed with operators applied to subexpressions of
    different types - the rules of C state very clearly how those
    subexpressions are converted.  (For most binary operators, these are
    the "usual arithmetic conversions".)  You know this too.

    An expression of mixed types means one that involves a number of
    different types amongst its types.

    (Here I meant 'amongst its terms'.)


    Okay, that's what /you/ mean by that phrase.  It is not an accurate description - in any statically typed language, an expression will have
    a single type.  Subexpressions can be different types.  But while I do
    not approve of your terms here, I do understand what you are talking about.

    Sure, the rules will tell you what the result will be, but you have to
    work it out, and to do that, you have to know what each of those types
    are (again, see above).

    No, the /compiler/ has to work it out.  Whether /you/ need to work it
    out or not, depends on what you are doing with the result.

    The compiler will not tell you the format codes to use!

    If you have "T x;", and you write "(unsigned long) x" (as Keith
    suggested), then you know the type of that expression - without knowing
    the type of T.  You need to know that "T" is a type that can be
    converted to "unsigned long" (any arithmetic or pointer type will do),
    and you need to know that the value of "x" is suitable for the
    conversion to be defined (so if "x" is floating point, it needs to be in range).  If you don't know at least that much about "x", you probably should not be writing code with it.

    I tried this program:

    #include <stdio.h>
    #include "t.h" // defines T

    T F();

    int main() {
    T x;
    x=F();
    printf("%lu\n", (unsigned long)x);
    }

    T happens to be 'int', and F() returns -1. This program however prints 4294967295.

    If I change it so that T is 'long long int' and F returns 5000000000,
    then it shows 705032704. Not really ideal.

    Here a better bet for an unknown type is %f, which gives the right
    values, but it appear as -1.00000 etc.

    Better still is to use exactly the right format, but that has the issues
    I mentioned.


    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From scott@scott@slp53.sl.home (Scott Lurndal) to comp.lang.c on Thu Feb 5 18:38:58 2026
    From Newsgroup: comp.lang.c

    Bart <bc@freeuk.com> writes:
    On 05/02/2026 11:41, David Brown wrote:
    On 04/02/2026 21:42, Bart wrote:

    Usually when you have a type T in your code, you know some things about
    it - you typically know if it is arithmetic, integer, floating point,
    you know something about its range.

    How about time_t, clock_t, off_t?

    What about them? time_t has a dedicated formatter
    (strftime) and parser (strptime). clock_t and
    off_t can be cast to the largest integer type (e.g. unsigned long)
    and use the printf '%lu' or "%ld' formatting sequence
    as required by the application.

    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Thu Feb 5 11:05:36 2026
    From Newsgroup: comp.lang.c

    David Brown <david.brown@hesbynett.no> writes:
    [...]
    How many people do you know who have actually written and use a
    C11 print system using _Generic and variadic macros? I don't know
    any. (I've written simple examples as proofs of concept, posted
    in this group, but not for real use.) It turns out that people
    /don't/ have to have workarounds. "printf" has its limitations -
    there's no doubt there. But it is good enough for most people
    and most uses.

    I recently played around with an attempted framework using _Generic.
    The goal was to be able to write something like

    print(s(x), s(y), s(z));

    where x, y, and z can be of more or less arbitrary types (integer, floating-point char*). The problem I ran into was that only one of
    the generic associations is evaluated (which one is determined at
    compile time), but *all* of them have to be valid code. There's a
    proposal to change this for C 202y.

    I didn't spend a lot of time on it.
    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Thu Feb 5 11:22:24 2026
    From Newsgroup: comp.lang.c

    David Brown <david.brown@hesbynett.no> writes:
    On 05/02/2026 00:52, Bart wrote:
    On 04/02/2026 23:39, Keith Thompson wrote:
    Bart <bc@freeuk.com> writes:
    [...]
    The problem is that C expects an exact format-code when trying to use
    *printf functions, and for that you need to know the exact types of
    the expressions being passed. For example:

       uintptr_t x;                    // from above examples
       double y;                       //
       printf("x * y is %?", x * y);   // What's '?'

    Since you asked...

    '?' is 'f' (or 'g' or 'e', or 'a', or any of those in upper case).
    `x * y` is of type double.

    The 'from above examples' applies to both x and y. That means that
    'double' /may/ have been redefined like this (from my post):

      #ifdef SQLITE_OMIT_FLOATING_POINT
      # define double sqlite3_int64
      #endif

    I don't know what 'sqlite3_int64' is, but it sounds like a signed
    integer. I was asked to give examples of conditional types, and
    thought it best to do so from actual programs.

    What you have found is an idiocy in SQLITE, not a problem in the C
    language or printf. If the macro "SQLITE_OMIT_FLOATING_POINT" is
    defined, then the type named "sqlite3_int64" is not an integer type,
    nor can it hold arbitrary 64-bit integers (as Michael S pointed out,
    and I assume accurately, it can hold 53 bit integers). I do not know
    what this type is used for in the code, but something like
    "sqlite3_bignum" would be a far better choice of name. And if it is
    intended that people print out these values directly, defining "PRsqlite3_bignum" to "%g" or "%llu" as appropriate would be helpful.
    (Yes, the resulting printf statements would be ugly - better ugly and
    correct than wrong).

    The macro doesn't define "sqlite3_int64", which as far as I can tell is
    always an integer type. It redefines "double".

    That macro in isolation does seem deeply silly, but I haven't worked on sqlite3's source code. Apparently the authors found it convenient.
    Presumably anyone working on the source code has to keep in mind that
    the word "double" doesn't necessarily mean what it normally means. It's
    not the way I would have written it. I probably would have defined a
    type name that can be either "double" or "sqlite3_int64", depending on
    the setting of SQLITE_OMIT_FLOATING_POINT. But I don't know enough
    about the sqlite3 source code to be able to meaningfully criticize it.

    In almost all contexts, it's perfectly reasonable to assume that the
    word "double" in C code refers to the predefined floating-point type.
    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Janis Papanagnou@janis_papanagnou+ng@hotmail.com to comp.lang.c on Thu Feb 5 23:55:22 2026
    From Newsgroup: comp.lang.c

    On 2026-02-05 18:42, Bart wrote:
    On 05/02/2026 11:41, David Brown wrote:

    No, the /compiler/ has to work it out.  Whether /you/ need to work it
    out or not, depends on what you are doing with the result.

    The compiler will not tell you the format codes to use!

    Well, it seems the compiler I have here does it quite verbosely...


    $ cc -o prtfmt prtfmt.c
    prtfmt.c: In function ‘main’:
    prtfmt.c:8:19: warning: format ‘%d’ expects argument of type ‘int’, but
    argument 2 has type ‘double’ [-Wformat=]
    8 | printf ("%d\n", f);
    | ~^ ~
    | | |
    | int double
    | %f
    prtfmt.c:9:19: warning: format ‘%f’ expects argument of type ‘double’, but argument 2 has type ‘int’ [-Wformat=]
    9 | printf ("%f\n", i);
    | ~^ ~
    | | |
    | | int
    | double
    | %d


    ...giving information of every kind - here for two basic types, but
    it has also the same verbose diagnostics with the '_t' types I tried
    (e.g. suggesting '%ld' for a 'time_t' argument).

    Note: I'm still acknowledging the unfortunate type/formatter-coupling notwithstanding.

    Janis

    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Bart@bc@freeuk.com to comp.lang.c on Thu Feb 5 23:42:06 2026
    From Newsgroup: comp.lang.c

    On 05/02/2026 22:55, Janis Papanagnou wrote:
    On 2026-02-05 18:42, Bart wrote:
    On 05/02/2026 11:41, David Brown wrote:

    No, the /compiler/ has to work it out.  Whether /you/ need to work it
    out or not, depends on what you are doing with the result.

    The compiler will not tell you the format codes to use!

    Well, it seems the compiler I have here does it quite verbosely...


    $ cc -o prtfmt prtfmt.c
    prtfmt.c: In function ‘main’:
    prtfmt.c:8:19: warning: format ‘%d’ expects argument of type ‘int’, but
    argument 2 has type ‘double’ [-Wformat=]
        8 |         printf ("%d\n", f);
          |                  ~^     ~
          |                   |     |
          |                   int   double
          |                  %f
    prtfmt.c:9:19: warning: format ‘%f’ expects argument of type ‘double’,
    but argument 2 has type ‘int’ [-Wformat=]
        9 |         printf ("%f\n", i);
          |                  ~^     ~
          |                   |     |
          |                   |     int
          |                   double
          |                  %d


    ...giving information of every kind - here for two basic types, but
    it has also the same verbose diagnostics with the '_t' types I tried
    (e.g. suggesting '%ld' for a 'time_t' argument).

    Note: I'm still acknowledging the unfortunate type/formatter-coupling notwithstanding.

    /Some/ compilers with /some/ options will /sometimes/ tell you when
    you've got it wrong.

    But you first have to make an educated guess, or put in some dummy
    format code.

    Eventually, it will compile. Until someone else builds your program,
    using a slightly different set of headers where certain types are
    defined, and then it might either give compiler messages that they have
    to fix, or it show wrong results.

    If I compile this code with 'gcc -Wall -Wextra -Wpedantic':

    #include <stdio.h>

    int main() {
    int a = -1;
    printf("%u", a);
    }

    it says nothing. The program displays 4294967295 instead of -1.

    If compile this version (using %v) using a special extension:

    #include <stdio.h>

    int main() {
    int a = -1;
    printf("%v", a);
    }

    it shows -1. Which is better?





    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Thu Feb 5 21:10:57 2026
    From Newsgroup: comp.lang.c

    Bart <bc@freeuk.com> writes:
    [...]
    /Some/ compilers with /some/ options will /sometimes/ tell you when
    you've got it wrong.

    But you first have to make an educated guess, or put in some dummy
    format code.

    Eventually, it will compile. Until someone else builds your program,
    using a slightly different set of headers where certain types are
    defined, and then it might either give compiler messages that they
    have to fix, or it show wrong results.

    That's not how I do it, and I don't think it's how most programmers do
    it.

    I know the rules well enough that I can usually write a correct format
    string in the first place. If I make a mistake, gcc's warnings are a
    nice check.

    If I compile this code with 'gcc -Wall -Wextra -Wpedantic':

    #include <stdio.h>

    int main() {
    int a = -1;
    printf("%u", a);
    }

    it says nothing. The program displays 4294967295 instead of -1.

    The behavior is unsurprising. The lack of a warning is very mildly inconvenient.

    If compile this version (using %v) using a special extension:

    #include <stdio.h>

    int main() {
    int a = -1;
    printf("%v", a);
    }

    it shows -1. Which is better?

    The one I can actually use.
    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Janis Papanagnou@janis_papanagnou+ng@hotmail.com to comp.lang.c on Fri Feb 6 09:51:07 2026
    From Newsgroup: comp.lang.c

    On 2026-02-06 06:10, Keith Thompson wrote:
    Bart <bc@freeuk.com> writes:

    If I compile this code with 'gcc -Wall -Wextra -Wpedantic':

    #include <stdio.h>

    int main() {
    int a = -1;
    printf("%u", a);
    }

    it says nothing. The program displays 4294967295 instead of -1.

    Yes. You instruct 'printf' with '%u' to interpret and display it
    (the variable 'a') as unsigned. ('-1' is not an unsigned numeric representation.) - I wonder what you are thinking here.


    The behavior is unsurprising. The lack of a warning is very mildly inconvenient.

    Indeed unsurprising. And I even don't see any inconvenience given
    that even an initialized declaration of 'unsigned a = -1;' is not
    considered a problem in "C". I rather learned that to be a useful
    code pattern when programming in "C".

    Janis

    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Fri Feb 6 02:25:21 2026
    From Newsgroup: comp.lang.c

    Bart <bc@freeuk.com> writes:

    On 04/02/2026 14:22, Tim Rentsch wrote:

    Bart <bc@freeuk.com> writes:

    On 03/02/2026 15:47, Tim Rentsch wrote:

    [...]

    If variable 'u' is declared as uint32_t, a way to print it that is
    easy and also type-safe is

    printf( " u is %lu\n", u+0LU );

    What about a compound expression of several variables of mixed
    integer types, possibly even mixed with floats, some of whose types
    might either be conditional (depending on some macro), or opaque?

    What is an example of a conditional/macro-dependent type?

    Example from SDL2:

    #if defined(_MSC_VER) && (_MSC_VER < 1600)
    ...
    #ifndef _UINTPTR_T_DEFINED
    #ifdef _WIN64
    typedef unsigned __int64 uintptr_t;
    #else
    typedef unsigned int uintptr_t;
    ...

    Example from SQLITE3:

    #ifdef SQLITE_OMIT_FLOATING_POINT
    # define double sqlite3_int64
    #endif


    Also what sort of opaque types do you have in mind?

    Things like time_t and clock_t, or the equivalent from libraries.

    Yes you could hunt down the exact underlying type (for clock_t in one
    case, it was under 6 layers of typedefs and macros), but that would be
    for a specific set of headers.

    For system headers, somebody could be using a header with different definitions. For user-libraries, it might be a slightly different
    version.


    What is the problem you want to solve here?

    The problem is that C expects an exact format-code when trying to use
    *printf functions, and for that you need to know the exact types of
    the expressions being passed. For example:

    uintptr_t x; // from above examples
    double y; //
    printf("x * y is %?", x * y); // What's '?'

    I understand. Thank you for the explanation.
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Fri Feb 6 02:59:01 2026
    From Newsgroup: comp.lang.c

    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:

    David Brown <david.brown@hesbynett.no> writes:
    [...]

    How many people do you know who have actually written and use a
    C11 print system using _Generic and variadic macros? I don't know
    any. (I've written simple examples as proofs of concept, posted
    in this group, but not for real use.) It turns out that people
    /don't/ have to have workarounds. "printf" has its limitations -
    there's no doubt there. But it is good enough for most people
    and most uses.

    I recently played around with an attempted framework using _Generic.
    The goal was to be able to write something like

    print(s(x), s(y), s(z));

    where x, y, and z can be of more or less arbitrary types (integer, floating-point char*). The problem I ran into was that only one of
    the generic associations is evaluated (which one is determined at
    compile time), but *all* of them have to be valid code.

    That is annoying but it shouldn't be too hard to work around
    it. To verify that hypothesis I wrote this test case:


    #include <stdio.h>
    #include <time.h>
    #include <stdint.h>

    #include "h/show.h"

    int
    main(){
    unsigned long long ull = -1;
    signed long long sll = -1;
    unsigned long ul = -1;
    signed long sl = -1;
    unsigned char uc = -1;
    signed char sc = -1;
    unsigned short us = -1;
    signed short ss = -1;
    unsigned int ui = -1;
    signed int si = -1;
    char c = 'q';
    float f = 1.23;
    double d = 3.14159265358979312;
    double long ld = 6.28318530717958623;

    _Bool yes = 1;
    _Bool no = !yes;

    clock_t runtime = clock();
    time_t now = time(0);
    off_t offset = 27;
    uint16_t u16 = -1;
    int16_t s16 = -1;
    uint_least32_t uge32 = -1;
    int_least32_t sge32 = -1;
    uint_fast32_t uf32 = -1;
    int_fast32_t sf32 = -1;
    char * foo = "foo";
    const char * bas = "bas";

    show(
    uc,sc,us,ss,ui,si,ul,sl,ull,sll,
    c,f,d,ld,yes,no,u16,s16,uge32,sge32,
    runtime,now,offset,uf32,sf32,
    c * now / 1e8 * ld,
    foo, bas
    );
    printf( "\n" );

    return 0;
    }

    which compiles under C11 and (along with the show.h include file)
    produces output:

    uc = 255
    sc = -1
    us = 65535
    ss = -1
    ui = 4294967295
    si = -1
    ul = 18446744073709551615
    sl = -1
    ull = 18446744073709551615
    sll = -1
    c = 'q'
    f = 1.230000
    d = 3.141593
    ld = 6.283185
    yes = true
    no = false
    u16 = 65535
    s16 = -1
    uge32 = 4294967295
    sge32 = -1
    runtime = 365
    now = 1770371790
    offset = 27
    uf32 = 18446744073709551615
    sf32 = -1
    c * now / 1e8 * ld = 12569.638642
    foo = "foo"
    bas = (const char *) "bas"
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Fri Feb 6 12:27:52 2026
    From Newsgroup: comp.lang.c

    On 05/02/2026 18:42, Bart wrote:
    On 05/02/2026 11:41, David Brown wrote:
    On 04/02/2026 21:42, Bart wrote:

    Usually when you have a type T in your code, you know some things
    about it - you typically know if it is arithmetic, integer, floating
    point, you know something about its range.

    How about time_t, clock_t, off_t?

    What about them? You know a lot about these types, and what they can be
    used for. You know if they are suitable for printf'ing directly, or
    not, and how to either convert them to a suitable type directly or to
    extract information from them.


      How much you know will vary, but a type you know absolutely nothing
    about is unlikely to be of any use in code.

    The problem is that that format code is tied to the type of the
    expression. That means that as your program evolves and the types
    change, or the expression changes (so another term's type becomes
    dominant), then you have to check all such format codes.


    If only there were a convenient way to handle this... oh, wait, there
    is. Do as any competent programmer does when they have a complex
    expression - assign the result to a local variable (/you/ pick the
    type), rather than having too much in one huge printf.


    Yes maybe that particular strategy might work (you know it is an
    integer and that it is unsigned).

    What did you think an "uint32_t" was, if not a type of unsigned
    integer?   And there is no "maybe" about it - the strategies work.

    See above.


    If you have other arithmetic types, then you need to adapt the
    strategy to fit - you need something that covers the ranges of the
    data you are dealing with.


    But it doesn't solve the general problem: even if there is a single >>>>> type involved, it might be conditional or opaque (or its type is
    changed required all format codes to be revised.

    Or there is an expression of mixed types.


    There is no such thing as an "expression of mixed types".  There are >>>> expressions formed with operators applied to subexpressions of
    different types - the rules of C state very clearly how those
    subexpressions are converted.  (For most binary operators, these are >>>> the "usual arithmetic conversions".)  You know this too.

    An expression of mixed types means one that involves a number of
    different types amongst its types.

    (Here I meant 'amongst its terms'.)


    Okay, that's what /you/ mean by that phrase.  It is not an accurate
    description - in any statically typed language, an expression will
    have a single type.  Subexpressions can be different types.  But while
    I do not approve of your terms here, I do understand what you are
    talking about.

    Sure, the rules will tell you what the result will be, but you have
    to work it out, and to do that, you have to know what each of those
    types are (again, see above).

    No, the /compiler/ has to work it out.  Whether /you/ need to work it
    out or not, depends on what you are doing with the result.

    The compiler will not tell you the format codes to use!

    If you have "T x;", and you write "(unsigned long) x" (as Keith
    suggested), then you know the type of that expression - without
    knowing the type of T.  You need to know that "T" is a type that can
    be converted to "unsigned long" (any arithmetic or pointer type will
    do), and you need to know that the value of "x" is suitable for the
    conversion to be defined (so if "x" is floating point, it needs to be
    in range).  If you don't know at least that much about "x", you
    probably should not be writing code with it.

    I tried this program:

      #include <stdio.h>
      #include "t.h"            // defines T

      T F();

      int main() {
         T x;
         x=F();
         printf("%lu\n", (unsigned long)x);
     }

    T happens to be 'int', and F() returns -1. This program however prints 4294967295.

    Converting -1 to unsigned long gives you that result, yes.

    Conversions do not absolve you from having to know what your code does,
    and the obligation to write sensible code. The conversion here means
    your code does not have C undefined behaviour - it means the compiler
    and run-time will do what you asked it to do. It cannot stop you from
    asking it to do something silly, or something you did not mean. Again,
    you /know/ this - it is fundamental to /all/ programming. It is nothing
    to do with C.


    If I change it so that T is 'long long int' and F returns 5000000000,
    then it shows 705032704. Not really ideal.

    Here a better bet for an unknown type is %f, which gives the right
    values, but it appear as -1.00000 etc.

    Better still is to use exactly the right format, but that has the issues
    I mentioned.



    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Fri Feb 6 12:42:28 2026
    From Newsgroup: comp.lang.c

    On 05/02/2026 20:05, Keith Thompson wrote:
    David Brown <david.brown@hesbynett.no> writes:
    [...]
    How many people do you know who have actually written and use a
    C11 print system using _Generic and variadic macros? I don't know
    any. (I've written simple examples as proofs of concept, posted
    in this group, but not for real use.) It turns out that people
    /don't/ have to have workarounds. "printf" has its limitations -
    there's no doubt there. But it is good enough for most people
    and most uses.

    I recently played around with an attempted framework using _Generic.
    The goal was to be able to write something like

    print(s(x), s(y), s(z));

    where x, y, and z can be of more or less arbitrary types (integer, floating-point char*). The problem I ran into was that only one of
    the generic associations is evaluated (which one is determined at
    compile time), but *all* of them have to be valid code. There's a
    proposal to change this for C 202y.

    I didn't spend a lot of time on it.


    My experiments used a variadic macro so that :

    print(x, y, z);

    would be turned into something akin to :

    print_generic(x);
    print_generic(y);
    print_generic(z);

    The "print_generic" _Generic macro would then lead you to :

    print_charp(x);
    print_double(y);
    print_int(z);

    The difference is then that you get multiple individual print calls,
    rather than one single one. For some uses, that could be a problem -
    for other uses, it could be fine. (For my own needs, it's actually
    quite okay - often what I will do is have a fixed-size local variable
    buffer, built up a debug string in that, then pass the buffer on to a
    serial port output routine. The main "print" macro would have this
    extra code before and after the print_generic macro calls.)

    It would also be possible to do something with _Generic macros to turn
    the different items into strings. You could allocate the memory for the strings with VLAs (or alloca, if you want to allow that). I haven't
    tried that, but maybe it's something for a rainy day.

    The most annoying thing about it all, however, is that there is no way
    to extend a _Generic macro later. You have to put all the types you
    want in the one place.

    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Fri Feb 6 12:46:56 2026
    From Newsgroup: comp.lang.c

    On 05/02/2026 20:22, Keith Thompson wrote:
    David Brown <david.brown@hesbynett.no> writes:
    On 05/02/2026 00:52, Bart wrote:
    On 04/02/2026 23:39, Keith Thompson wrote:
    Bart <bc@freeuk.com> writes:
    [...]
    The problem is that C expects an exact format-code when trying to use >>>>> *printf functions, and for that you need to know the exact types of
    the expressions being passed. For example:

       uintptr_t x;                    // from above examples
       double y;                       //
       printf("x * y is %?", x * y);   // What's '?'

    Since you asked...

    '?' is 'f' (or 'g' or 'e', or 'a', or any of those in upper case).
    `x * y` is of type double.

    The 'from above examples' applies to both x and y. That means that
    'double' /may/ have been redefined like this (from my post):

      #ifdef SQLITE_OMIT_FLOATING_POINT
      # define double sqlite3_int64
      #endif

    I don't know what 'sqlite3_int64' is, but it sounds like a signed
    integer. I was asked to give examples of conditional types, and
    thought it best to do so from actual programs.

    What you have found is an idiocy in SQLITE, not a problem in the C
    language or printf. If the macro "SQLITE_OMIT_FLOATING_POINT" is
    defined, then the type named "sqlite3_int64" is not an integer type,
    nor can it hold arbitrary 64-bit integers (as Michael S pointed out,
    and I assume accurately, it can hold 53 bit integers). I do not know
    what this type is used for in the code, but something like
    "sqlite3_bignum" would be a far better choice of name. And if it is
    intended that people print out these values directly, defining
    "PRsqlite3_bignum" to "%g" or "%llu" as appropriate would be helpful.
    (Yes, the resulting printf statements would be ugly - better ugly and
    correct than wrong).

    The macro doesn't define "sqlite3_int64", which as far as I can tell is always an integer type. It redefines "double".

    You are right - I was reading it as a typedef.

    Redefining "double" with a macro expanding to an integer type (if that
    is what "sqlite3_int64" is) is an even worse idea than my misinterpretation!


    That macro in isolation does seem deeply silly, but I haven't worked on sqlite3's source code. Apparently the authors found it convenient. Presumably anyone working on the source code has to keep in mind that
    the word "double" doesn't necessarily mean what it normally means. It's
    not the way I would have written it. I probably would have defined a
    type name that can be either "double" or "sqlite3_int64", depending on
    the setting of SQLITE_OMIT_FLOATING_POINT. But I don't know enough
    about the sqlite3 source code to be able to meaningfully criticize it.


    I think we all know enough about C to criticise this. Defining a macro
    with the name of a keyword is UB - even if we assume that all people
    reading the sqlite code understand what is going on.

    In almost all contexts, it's perfectly reasonable to assume that the
    word "double" in C code refers to the predefined floating-point type.


    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Bart@bc@freeuk.com to comp.lang.c on Fri Feb 6 12:39:55 2026
    From Newsgroup: comp.lang.c

    On 06/02/2026 05:10, Keith Thompson wrote:
    Bart <bc@freeuk.com> writes:
    [...]
    /Some/ compilers with /some/ options will /sometimes/ tell you when
    you've got it wrong.

    But you first have to make an educated guess, or put in some dummy
    format code.

    Eventually, it will compile. Until someone else builds your program,
    using a slightly different set of headers where certain types are
    defined, and then it might either give compiler messages that they
    have to fix, or it show wrong results.

    That's not how I do it, and I don't think it's how most programmers do
    it.

    I know the rules well enough that I can usually write a correct format
    string in the first place. If I make a mistake, gcc's warnings are a
    nice check.

    I guess you've never used printf-family functions via the FFI of another language!


    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Fri Feb 6 04:46:51 2026
    From Newsgroup: comp.lang.c

    Janis Papanagnou <janis_papanagnou+ng@hotmail.com> writes:
    On 2026-02-06 06:10, Keith Thompson wrote:
    Bart <bc@freeuk.com> writes:

    If I compile this code with 'gcc -Wall -Wextra -Wpedantic':

    #include <stdio.h>

    int main() {
    int a = -1;
    printf("%u", a);
    }

    it says nothing. The program displays 4294967295 instead of -1.

    Yes. You instruct 'printf' with '%u' to interpret and display it
    (the variable 'a') as unsigned. ('-1' is not an unsigned numeric representation.) - I wonder what you are thinking here.

    The behavior is unsurprising. The lack of a warning is very mildly
    inconvenient.

    Indeed unsurprising. And I even don't see any inconvenience given
    that even an initialized declaration of 'unsigned a = -1;' is not
    considered a problem in "C". I rather learned that to be a useful
    code pattern when programming in "C".

    Sure, but the point is that the program has undefined behavior.

    If you define `unsigned a = -1;`, the value of the initializer is
    implicitly converted from int to unsigned, and the value of UINT_MAX
    is stored in `a`. That's well defined.

    Passing a argument of type int to printf with a "%u" format is
    well defined if and only if the value of the argument is within
    the ranges of both types (which is almost certainly equivalent to
    it being non-negative). This is strongly implied prior to C23,
    and guaranteed in C23. There is no implicit conversion; the int
    is treated as if it were of type unsigned int. In practice, it's
    almost certain to display the value of UINT_MAX unless the compiler
    goes out of its way to do something else.

    A warning would not be inappropriate -- and in fact clang version
    21 does issue a warning with "-Weverything". (The warning refers to "-Wformat", which by itself doesn't trigger the warning; that might
    be a bug.)
    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Michael S@already5chosen@yahoo.com to comp.lang.c on Fri Feb 6 14:47:32 2026
    From Newsgroup: comp.lang.c

    On Fri, 6 Feb 2026 12:39:55 +0000
    Bart <bc@freeuk.com> wrote:

    On 06/02/2026 05:10, Keith Thompson wrote:
    Bart <bc@freeuk.com> writes:
    [...]
    /Some/ compilers with /some/ options will /sometimes/ tell you when
    you've got it wrong.

    But you first have to make an educated guess, or put in some dummy
    format code.

    Eventually, it will compile. Until someone else builds your
    program, using a slightly different set of headers where certain
    types are defined, and then it might either give compiler messages
    that they have to fix, or it show wrong results.

    That's not how I do it, and I don't think it's how most programmers
    do it.

    I know the rules well enough that I can usually write a correct
    format string in the first place. If I make a mistake, gcc's
    warnings are a nice check.

    I guess you've never used printf-family functions via the FFI of
    another language!



    Vararg via FFI? Is it really a good idea?

    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Fri Feb 6 04:49:54 2026
    From Newsgroup: comp.lang.c

    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    [...]
    I recently played around with an attempted framework using _Generic.
    The goal was to be able to write something like

    print(s(x), s(y), s(z));

    where x, y, and z can be of more or less arbitrary types (integer,
    floating-point char*). The problem I ran into was that only one of
    the generic associations is evaluated (which one is determined at
    compile time), but *all* of them have to be valid code.

    That is annoying but it shouldn't be too hard to work around
    it. To verify that hypothesis I wrote this test case:


    #include <stdio.h>
    #include <time.h>
    #include <stdint.h>

    #include "h/show.h"

    int
    main(){
    [30 lines deleted]
    show(
    uc,sc,us,ss,ui,si,ul,sl,ull,sll,
    c,f,d,ld,yes,no,u16,s16,uge32,sge32,
    runtime,now,offset,uf32,sf32,
    c * now / 1e8 * ld,
    foo, bas
    );
    printf( "\n" );

    return 0;
    }

    which compiles under C11 and (along with the show.h include file)
    produces output:

    uc = 255
    sc = -1
    us = 65535
    [23 lines deleted]
    foo = "foo"
    bas = (const char *) "bas"

    Were you planning to show us what show.h looks like?
    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Bart@bc@freeuk.com to comp.lang.c on Fri Feb 6 13:04:34 2026
    From Newsgroup: comp.lang.c

    On 06/02/2026 12:47, Michael S wrote:
    On Fri, 6 Feb 2026 12:39:55 +0000
    Bart <bc@freeuk.com> wrote:

    On 06/02/2026 05:10, Keith Thompson wrote:
    Bart <bc@freeuk.com> writes:
    [...]
    /Some/ compilers with /some/ options will /sometimes/ tell you when
    you've got it wrong.

    But you first have to make an educated guess, or put in some dummy
    format code.

    Eventually, it will compile. Until someone else builds your
    program, using a slightly different set of headers where certain
    types are defined, and then it might either give compiler messages
    that they have to fix, or it show wrong results.

    That's not how I do it, and I don't think it's how most programmers
    do it.

    I know the rules well enough that I can usually write a correct
    format string in the first place. If I make a mistake, gcc's
    warnings are a nice check.

    I guess you've never used printf-family functions via the FFI of
    another language!



    Vararg via FFI? Is it really a good idea?


    It's covered by platform ABIs of both Windows and SYS V.

    This is printf called from an interpreted language with dynamic typing:

    a := 12345678987654321
    b := pi
    c := "A"*10
    d := &a
    printf("%lld %f %s %p\n", a, b, c, d)

    Output is:

    12345678987654321 3.141593 AAAAAAAAAA 00000000036A1D48

    (Strings are converted to zero-terminated form for the FFI.)

    If I try it like this however:

    printf("%lld %f %s %p\n", d, c, b, a)

    It will go wrong (crashing inside the C library). With the built-in
    print feature, that doesn't happen:

    println a, b, c, d
    println d, c, b, a


    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Bart@bc@freeuk.com to comp.lang.c on Fri Feb 6 13:05:48 2026
    From Newsgroup: comp.lang.c

    On 06/02/2026 12:49, Keith Thompson wrote:
    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    [...]
    I recently played around with an attempted framework using _Generic.
    The goal was to be able to write something like

    print(s(x), s(y), s(z));

    where x, y, and z can be of more or less arbitrary types (integer,
    floating-point char*). The problem I ran into was that only one of
    the generic associations is evaluated (which one is determined at
    compile time), but *all* of them have to be valid code.

    That is annoying but it shouldn't be too hard to work around
    it. To verify that hypothesis I wrote this test case:


    #include <stdio.h>
    #include <time.h>
    #include <stdint.h>

    #include "h/show.h"

    int
    main(){
    [30 lines deleted]
    show(
    uc,sc,us,ss,ui,si,ul,sl,ull,sll,
    c,f,d,ld,yes,no,u16,s16,uge32,sge32,
    runtime,now,offset,uf32,sf32,
    c * now / 1e8 * ld,
    foo, bas
    );
    printf( "\n" );

    return 0;
    }

    which compiles under C11 and (along with the show.h include file)
    produces output:

    uc = 255
    sc = -1
    us = 65535
    [23 lines deleted]
    foo = "foo"
    bas = (const char *) "bas"

    Were you planning to show us what show.h looks like?

    Of course not!


    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Fri Feb 6 14:06:25 2026
    From Newsgroup: comp.lang.c

    On 06/02/2026 13:47, Michael S wrote:
    On Fri, 6 Feb 2026 12:39:55 +0000
    Bart <bc@freeuk.com> wrote:

    On 06/02/2026 05:10, Keith Thompson wrote:
    Bart <bc@freeuk.com> writes:
    [...]
    /Some/ compilers with /some/ options will /sometimes/ tell you when
    you've got it wrong.

    But you first have to make an educated guess, or put in some dummy
    format code.

    Eventually, it will compile. Until someone else builds your
    program, using a slightly different set of headers where certain
    types are defined, and then it might either give compiler messages
    that they have to fix, or it show wrong results.

    That's not how I do it, and I don't think it's how most programmers
    do it.

    I know the rules well enough that I can usually write a correct
    format string in the first place. If I make a mistake, gcc's
    warnings are a nice check.

    I guess you've never used printf-family functions via the FFI of
    another language!



    Vararg via FFI? Is it really a good idea?


    No.

    Every language I have used has had its own way to handle printing.
    Usually that is more convenient, in at least some aspects, than C's
    printf family.

    But if some other language wants to do its printing by calling C's
    printf, and that leads to troubles or limitations, that's the fault of
    the other language.

    (We can recall that Bart has regularly blamed C for issues,
    complications or limitations in his own language - as though the
    original designers of C had an obligation to make it easier for Bart to
    make tools for a completely different language.)

    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Fri Feb 6 05:08:35 2026
    From Newsgroup: comp.lang.c

    Bart <bc@freeuk.com> writes:
    [...]
    I guess you've never used printf-family functions via the FFI of
    another language!

    As it happens, I haven't.

    I presume if there were a point, you would have made it by now.
    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Bart@bc@freeuk.com to comp.lang.c on Fri Feb 6 14:28:46 2026
    From Newsgroup: comp.lang.c

    On 06/02/2026 13:08, Keith Thompson wrote:
    Bart <bc@freeuk.com> writes:
    [...]
    I guess you've never used printf-family functions via the FFI of
    another language!

    As it happens, I haven't.

    I presume if there were a point, you would have made it by now.



    I thought you might have infered it.

    All shortcomings of C can apparently be fixed by employing a selection
    of it gcc's 200+ options. So no point in making the language better instead.

    But that doesn't work when some bits of raw C need to be used from
    another language. For example, library headers containing a C API, which
    some languages may use as the basis for their own bindings.

    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Bart@bc@freeuk.com to comp.lang.c on Fri Feb 6 14:29:49 2026
    From Newsgroup: comp.lang.c

    On 06/02/2026 14:28, Bart wrote:
    On 06/02/2026 13:08, Keith Thompson wrote:
    Bart <bc@freeuk.com> writes:
    [...]
    I guess you've never used printf-family functions via the FFI of
    another language!

    As it happens, I haven't.

    I presume if there were a point, you would have made it by now.



    I thought you might have infered it.

    All shortcomings of C can apparently be fixed by employing a selection
    of it gcc's 200+ options.

    I missed out at least one zero!
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From scott@scott@slp53.sl.home (Scott Lurndal) to comp.lang.c on Fri Feb 6 14:35:46 2026
    From Newsgroup: comp.lang.c

    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    Bart <bc@freeuk.com> writes:
    [...]
    /Some/ compilers with /some/ options will /sometimes/ tell you when
    you've got it wrong.

    But you first have to make an educated guess, or put in some dummy
    format code.

    Eventually, it will compile. Until someone else builds your program,
    using a slightly different set of headers where certain types are
    defined, and then it might either give compiler messages that they
    have to fix, or it show wrong results.

    That's not how I do it, and I don't think it's how most programmers do
    it.

    I know the rules well enough that I can usually write a correct format
    string in the first place. If I make a mistake, gcc's warnings are a
    nice check.

    Agreed.


    If I compile this code with 'gcc -Wall -Wextra -Wpedantic':

    #include <stdio.h>

    int main() {
    int a = -1;
    printf("%u", a);
    }

    it says nothing. The program displays 4294967295 instead of -1.

    The behavior is unsurprising. The lack of a warning is very mildly >inconvenient.

    A warning could be even worse; it may not be unusual to
    desire to print a signed integer as unsigned (or in hex) in
    some applications.

    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Fri Feb 6 11:21:44 2026
    From Newsgroup: comp.lang.c

    Bart <bc@freeuk.com> writes:
    On 06/02/2026 13:08, Keith Thompson wrote:
    Bart <bc@freeuk.com> writes:
    [...]
    I guess you've never used printf-family functions via the FFI of
    another language!
    As it happens, I haven't.
    I presume if there were a point, you would have made it by now.

    I thought you might have infered it.

    All shortcomings of C can apparently be fixed by employing a selection
    of it gcc's 200+ options. So no point in making the language better
    instead.

    Nobody said any of that.

    But that doesn't work when some bits of raw C need to be used from
    another language. For example, library headers containing a C API,
    which some languages may use as the basis for their own bindings.

    Sure, FFIs can be tricky.

    You randomly introduced FFIs into a discussion of printf formats. What irrelevant argument are you going to make next?

    https://en.wikipedia.org/wiki/Gish_gallop
    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Bart@bc@freeuk.com to comp.lang.c on Fri Feb 6 20:08:57 2026
    From Newsgroup: comp.lang.c

    On 06/02/2026 19:21, Keith Thompson wrote:
    Bart <bc@freeuk.com> writes:
    On 06/02/2026 13:08, Keith Thompson wrote:
    Bart <bc@freeuk.com> writes:
    [...]
    I guess you've never used printf-family functions via the FFI of
    another language!
    As it happens, I haven't.
    I presume if there were a point, you would have made it by now.

    I thought you might have infered it.

    All shortcomings of C can apparently be fixed by employing a selection
    of it gcc's 200+ options. So no point in making the language better
    instead.

    Nobody said any of that.

    But that doesn't work when some bits of raw C need to be used from
    another language. For example, library headers containing a C API,
    which some languages may use as the basis for their own bindings.

    Sure, FFIs can be tricky.

    You randomly introduced FFIs into a discussion of printf formats. What irrelevant argument are you going to make next?

    https://en.wikipedia.org/wiki/Gish_gallop

    You seem to have introduced some nonsense of your own.

    I'm simply saying that people discussing here C are often blind to its problems because they employ an advanced C compiler or other analytical
    tools to mitigate them.

    You can't do that if working with raw C like I do. I've been using C
    libraries via FFIs and C header files for about 30 years.

    And so, if you want to use *printf functions like that, then the fact
    that gcc can sometimes report on incorrect format codes is no help at
    all; I'm not using gcc, /or/ writing C!

    It doesn't help when I'm writing C either, as I either use lesser
    compilers, or use gcc without any fancy options.

    I believe a language should stand by itself and not rely on complex
    tooling to make it practical to use. Not even syntax highlighting should
    be necessary.



    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Fri Feb 6 12:56:01 2026
    From Newsgroup: comp.lang.c

    Bart <bc@freeuk.com> writes:
    On 06/02/2026 19:21, Keith Thompson wrote:
    Bart <bc@freeuk.com> writes:
    On 06/02/2026 13:08, Keith Thompson wrote:
    Bart <bc@freeuk.com> writes:
    [...]
    I guess you've never used printf-family functions via the FFI of
    another language!

    As it happens, I haven't.

    I presume if there were a point, you would have made it by now.

    I thought you might have infered it.

    All shortcomings of C can apparently be fixed by employing a selection
    of it gcc's 200+ options. So no point in making the language better
    instead.

    Nobody said any of that.

    But that doesn't work when some bits of raw C need to be used from
    another language. For example, library headers containing a C API,
    which some languages may use as the basis for their own bindings.

    Sure, FFIs can be tricky.

    You randomly introduced FFIs into a discussion of printf formats. What
    irrelevant argument are you going to make next?
    https://en.wikipedia.org/wiki/Gish_gallop

    You seem to have introduced some nonsense of your own.

    What nonsense have I introduced? please be specific.

    I'm simply saying that people discussing here C are often blind to its problems because they employ an advanced C compiler or other
    analytical tools to mitigate them.

    People discussing C here are typically very much aware of its problems.
    Much of what we discuss is methods for using C correctly and, yes,
    working around its problems.

    You can't do that if working with raw C like I do. I've been using C libraries via FFIs and C header files for about 30 years.

    Good for you. Most of the rest of us don't use FFIs. But if you
    want help using them, you could ask specific questions, and it's
    likely that someone here would be willing and able to answer them.

    And so, if you want to use *printf functions like that, then the fact
    that gcc can sometimes report on incorrect format codes is no help at
    all; I'm not using gcc, /or/ writing C!

    OK, so some warnings produced by gcc (and other compilers) that I find
    useful are not useful to you.

    So what? What do you expect anyone here to do about it?

    You seem to be complaining that calling C's printf from another
    language via an FFI is difficult. I can certainly believe that
    it is. Do you actually need to do that? I have some thoughts
    about alternative approaches, which I'll be glad to discuss --
    if and only if you ask.

    It doesn't help when I'm writing C either, as I either use lesser
    compilers, or use gcc without any fancy options.

    I believe a language should stand by itself and not rely on complex
    tooling to make it practical to use. Not even syntax highlighting
    should be necessary.

    News flash: Bart doesn't like C.
    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Kaz Kylheku@046-301-5902@kylheku.com to comp.lang.c on Sat Feb 7 17:55:23 2026
    From Newsgroup: comp.lang.c

    On 2026-02-05, Bart <bc@freeuk.com> wrote:
    On 05/02/2026 22:55, Janis Papanagnou wrote:
    On 2026-02-05 18:42, Bart wrote:
    On 05/02/2026 11:41, David Brown wrote:

    No, the /compiler/ has to work it out.  Whether /you/ need to work it >>>> out or not, depends on what you are doing with the result.

    The compiler will not tell you the format codes to use!

    Well, it seems the compiler I have here does it quite verbosely...


    $ cc -o prtfmt prtfmt.c
    prtfmt.c: In function ‘main’:
    prtfmt.c:8:19: warning: format ‘%d’ expects argument of type ‘int’, but
    argument 2 has type ‘double’ [-Wformat=]
        8 |         printf ("%d\n", f);
          |                  ~^     ~
          |                   |     |
          |                   int   double
          |                  %f
    prtfmt.c:9:19: warning: format ‘%f’ expects argument of type ‘double’,
    but argument 2 has type ‘int’ [-Wformat=]
        9 |         printf ("%f\n", i);
          |                  ~^     ~
          |                   |     |
          |                   |     int
          |                   double
          |                  %d


    ...giving information of every kind - here for two basic types, but
    it has also the same verbose diagnostics with the '_t' types I tried
    (e.g. suggesting '%ld' for a 'time_t' argument).

    Note: I'm still acknowledging the unfortunate type/formatter-coupling
    notwithstanding.

    /Some/ compilers with /some/ options will /sometimes/ tell you when
    you've got it wrong.

    That's an excellent reason to keep the bulk of your code portable, and
    offer it to multiple compilers.

    I think the only way you are going to run into a crappy compiler in a
    real job situation in 2026 is if you're an embedded developer working
    with some very proprietary processor for which the only compiler comes
    from its vendor. Even so the bits of your code not specific to that
    chip can be compiled with something else. Which you want to do not just
    for diagnostics but to be able to run unit tests on that code on a
    regular developer machine.

    Eventually, it will compile. Until someone else builds your program,
    using a slightly different set of headers where certain types are
    defined, and then it might either give compiler messages that they have
    to fix, or it show wrong results.

    If I compile this code with 'gcc -Wall -Wextra -Wpedantic':

    #include <stdio.h>

    int main() {
    int a = -1;
    printf("%u", a);
    }

    it says nothing. The program displays 4294967295 instead of -1.

    For that you need this:

    $ gcc -Wall -pedantic -W -Wformat -Wformat-signedness printf.c
    printf.c: In function ‘main’:
    printf.c:5:12: warning: format ‘%u’ expects argument of type ‘unsigned int’, but argument 2 has type ‘int’ [-Wformat=]
    printf("%u", a);
    ~^
    %u


    There is probably a good reason for that; passing a signed argument
    to an unsigned conversion specifier de facto works fine, and
    some code relies on it; i.e. the 4294967295 is what the programmer
    wanted.

    You often see that with %x, which also takes unsigned int;
    the programmer wants -16 to come out as "FFFFFFF0", and not -10.

    Someone with code like that might want to catch other problems with
    printf calls, and not be bothered with those.

    If compile this version (using %v) using a special extension:

    #include <stdio.h>

    int main() {
    int a = -1;
    printf("%v", a);
    }

    it shows -1. Which is better?

    Both are undefined behavior. The latter is a documented extension
    that works where it works, which is good.

    Using %u with int /de facto/ works (and could also be a documented
    extension).

    /de facto/ is weaker than documented. But on the other hand, /de facto/
    works in more places than %v.

    If you hit a library that doesn't have %v, it doesn't work at all.

    I've never seen int passed to %d or %x not work in the manner you
    would expect if int and unsigned int arguments were passed in
    exactly the same way and subject to a reinterpretation of the bits.
    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @Kazinator@mstdn.ca
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Kaz Kylheku@046-301-5902@kylheku.com to comp.lang.c on Sat Feb 7 18:07:48 2026
    From Newsgroup: comp.lang.c

    On 2026-02-06, Michael S <already5chosen@yahoo.com> wrote:
    On Fri, 6 Feb 2026 12:39:55 +0000
    Bart <bc@freeuk.com> wrote:

    On 06/02/2026 05:10, Keith Thompson wrote:
    Bart <bc@freeuk.com> writes:
    [...]
    /Some/ compilers with /some/ options will /sometimes/ tell you when
    you've got it wrong.

    But you first have to make an educated guess, or put in some dummy
    format code.

    Eventually, it will compile. Until someone else builds your
    program, using a slightly different set of headers where certain
    types are defined, and then it might either give compiler messages
    that they have to fix, or it show wrong results.

    That's not how I do it, and I don't think it's how most programmers
    do it.

    I know the rules well enough that I can usually write a correct
    format string in the first place. If I make a mistake, gcc's
    warnings are a nice check.

    I guess you've never used printf-family functions via the FFI of
    another language!



    Vararg via FFI? Is it really a good idea?

    I support it in TXR Lisp. However, it's limited in that the FFI
    definition is nailed to a particular choice of arguments.

    For instance we could make a function foo which takes two arguments:
    a str and an int, and calls the variadic printf.

    Then we can call (foo "%d" 42). It will call printf("%d", 42).

    We cannot pass fewer or more than two arguments to foo, and they have to
    be compatible with str and int.

    Demo:

    $ txr
    This is the TXR Lisp interactive listener of TXR 302.
    Quit with :quit or Ctrl-D on an empty line. Ctrl-X ? for cheatsheet.
    (with-dyn-lib nil
    (deffi printf-int "printf" int (str : int)))
    printf-int
    (printf-int "%d\n" 42)
    42
    3

    42 is output; 3 is the result value (3 characters output).

    The : syntax in the deffi macro call indicates the variadic list.
    It's not the case that we can make a variadic Lisp function pass its arguments as an arbitrarily long variadic list with arbitrary types to the wrapped FFI function. Fixed parameters must be declared after the colon.

    A dynamic treatment could be arranged via a heavy weight wrapper mechanism which
    dynamically analyzes the actual arguments, builds a libffi function descriptor on the fly, then uses it to make the call; it could be wortwhile for someone, but I didn't implement such a thing. Metaprogramming tricks revolving around dynamically evaluating deffi are also possible.
    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @Kazinator@mstdn.ca
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Bart@bc@freeuk.com to comp.lang.c on Sat Feb 7 20:32:06 2026
    From Newsgroup: comp.lang.c

    On 07/02/2026 18:07, Kaz Kylheku wrote:
    On 2026-02-06, Michael S <already5chosen@yahoo.com> wrote:
    On Fri, 6 Feb 2026 12:39:55 +0000
    Bart <bc@freeuk.com> wrote:

    On 06/02/2026 05:10, Keith Thompson wrote:
    Bart <bc@freeuk.com> writes:
    [...]
    /Some/ compilers with /some/ options will /sometimes/ tell you when
    you've got it wrong.

    But you first have to make an educated guess, or put in some dummy
    format code.

    Eventually, it will compile. Until someone else builds your
    program, using a slightly different set of headers where certain
    types are defined, and then it might either give compiler messages
    that they have to fix, or it show wrong results.

    That's not how I do it, and I don't think it's how most programmers
    do it.

    I know the rules well enough that I can usually write a correct
    format string in the first place. If I make a mistake, gcc's
    warnings are a nice check.

    I guess you've never used printf-family functions via the FFI of
    another language!



    Vararg via FFI? Is it really a good idea?

    I support it in TXR Lisp. However, it's limited in that the FFI
    definition is nailed to a particular choice of arguments.

    For instance we could make a function foo which takes two arguments:
    a str and an int, and calls the variadic printf.

    Then we can call (foo "%d" 42). It will call printf("%d", 42).

    We cannot pass fewer or more than two arguments to foo, and they have to
    be compatible with str and int.

    Demo:

    $ txr
    This is the TXR Lisp interactive listener of TXR 302.
    Quit with :quit or Ctrl-D on an empty line. Ctrl-X ? for cheatsheet.
    (with-dyn-lib nil
    (deffi printf-int "printf" int (str : int)))
    printf-int
    (printf-int "%d\n" 42)
    42
    3

    42 is output; 3 is the result value (3 characters output).

    The : syntax in the deffi macro call indicates the variadic list.
    It's not the case that we can make a variadic Lisp function pass its arguments
    as an arbitrarily long variadic list with arbitrary types to the wrapped FFI function. Fixed parameters must be declared after the colon.

    There's another issue with calling variadic functions, unrelated to the
    number of args. I can't tell from the above whether it's convered.

    Normally an arg that is passed in a register, will be passed in GPR for integer, or a floating point register if not.

    But a variadic float argument has to be passed in both, so for Win64
    ABI/x64 it might be in both rcx and xmm1. I think it is similar on SYS V
    for both x64 and arm64 (maybe on the latter both are passed in the GPR;
    I'd have to go and look it up).


    A dynamic treatment could be arranged via a heavy weight wrapper mechanism which
    dynamically analyzes the actual arguments, builds a libffi function descriptor
    on the fly, then uses it to make the call; it could be wortwhile for someone, but I didn't implement such a thing. Metaprogramming tricks revolving around dynamically evaluating deffi are also possible.

    My LIBFFI approach just uses assembly; it's the simplest way to do it.
    (The LIBFFI 'C' library also uses assembly to do the tricky bits.)

    There, for Win64 ABI, I found it easiest to just load all the register
    args to both integer and float registers, whether the called function
    was variadic or not. That's far more efficient than figuring out the
    right register argument by argument.

    I haven't implemented that for SYS V; that's more of a nightmare ABI
    where up to 6-12 args (8-16 on aarch64) can be passed between int and
    float registers depending on the mix of types.

    On Win64 ABI, it is 4 args, always.



    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Bart@bc@freeuk.com to comp.lang.c on Sat Feb 7 20:51:53 2026
    From Newsgroup: comp.lang.c

    On 07/02/2026 17:55, Kaz Kylheku wrote:
    On 2026-02-05, Bart <bc@freeuk.com> wrote:
    On 05/02/2026 22:55, Janis Papanagnou wrote:
    On 2026-02-05 18:42, Bart wrote:
    On 05/02/2026 11:41, David Brown wrote:

    No, the /compiler/ has to work it out.  Whether /you/ need to work it >>>>> out or not, depends on what you are doing with the result.

    The compiler will not tell you the format codes to use!

    Well, it seems the compiler I have here does it quite verbosely...


    $ cc -o prtfmt prtfmt.c
    prtfmt.c: In function ‘main’:
    prtfmt.c:8:19: warning: format ‘%d’ expects argument of type ‘int’, but
    argument 2 has type ‘double’ [-Wformat=]
        8 |         printf ("%d\n", f);
          |                  ~^     ~
          |                   |     |
          |                   int   double
          |                  %f
    prtfmt.c:9:19: warning: format ‘%f’ expects argument of type ‘double’,
    but argument 2 has type ‘int’ [-Wformat=]
        9 |         printf ("%f\n", i);
          |                  ~^     ~
          |                   |     |
          |                   |     int
          |                   double
          |                  %d


    ...giving information of every kind - here for two basic types, but
    it has also the same verbose diagnostics with the '_t' types I tried
    (e.g. suggesting '%ld' for a 'time_t' argument).

    Note: I'm still acknowledging the unfortunate type/formatter-coupling
    notwithstanding.

    /Some/ compilers with /some/ options will /sometimes/ tell you when
    you've got it wrong.

    That's an excellent reason to keep the bulk of your code portable, and
    offer it to multiple compilers.

    I think the only way you are going to run into a crappy compiler in a
    real job situation in 2026 is if you're an embedded developer working
    with some very proprietary processor for which the only compiler comes
    from its vendor. Even so the bits of your code not specific to that
    chip can be compiled with something else. Which you want to do not just
    for diagnostics but to be able to run unit tests on that code on a
    regular developer machine.

    Eventually, it will compile. Until someone else builds your program,
    using a slightly different set of headers where certain types are
    defined, and then it might either give compiler messages that they have
    to fix, or it show wrong results.

    If I compile this code with 'gcc -Wall -Wextra -Wpedantic':

    #include <stdio.h>

    int main() {
    int a = -1;
    printf("%u", a);
    }

    it says nothing. The program displays 4294967295 instead of -1.

    For that you need this:

    $ gcc -Wall -pedantic -W -Wformat -Wformat-signedness printf.c

    -Wformat-signedness, of course! Sorry I just don't believe in
    micro-managing a compiler's job to that extent.

    Here's my code: is it valid C or not? It's a yes or no answer.

    If I wanted a more nuanced or a speculative opinion about my code, I'd
    use a tool that wasn't called a compiler, and I would not use it for
    every routine build.
    There is probably a good reason for that; passing a signed argument
    to an unsigned conversion specifier de facto works fine, and
    some code relies on it; i.e. the 4294967295 is what the programmer
    wanted.

    Then they can add a cast.


    You often see that with %x, which also takes unsigned int;
    the programmer wants -16 to come out as "FFFFFFF0", and not -10.

    That's whay you get with %x anyway; I've never seen it produce a
    negative hex number.

    (My systems language can produce negative hex results, unless told to
    treat as unsigned. Example:

    i64 a := -1
    u64 b := -1

    println a:"h" # -1
    println a:"hu" # FFFFFFFFFFFFFFFF
    println b:"h" # FFFFFFFFFFFFFFFF)


    Someone with code like that might want to catch other problems with
    printf calls, and not be bothered with those.

    If compile this version (using %v) using a special extension:

    #include <stdio.h>

    int main() {
    int a = -1;
    printf("%v", a);
    }

    it shows -1. Which is better?

    Both are undefined behavior. The latter is a documented extension
    that works where it works, which is good.

    Using %u with int /de facto/ works (and could also be a documented extension).

    /de facto/ is weaker than documented. But on the other hand, /de facto/
    works in more places than %v.

    If you hit a library that doesn't have %v, it doesn't work at all.

    The %v refered to one of my old implementations.

    There was handled within the compiler, so worked with any library.

    It was limited to format strings that are constants, but then so are -Wformat-signedness etc.


    I've never seen int passed to %d or %x not work in the manner you
    would expect if int and unsigned int arguments were passed in
    exactly the same way and subject to a reinterpretation of the bits.


    I've been in the situation where I was unsure if the result of an
    expression was signed or unsigned. So if the top if is set, I want to
    see if it was printed as a negative value (so signed), or as positive.

    %u or %x won't help here. I head to resort to casting to float then
    using %f.
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From antispam@antispam@fricas.org (Waldek Hebisch) to comp.lang.c on Sat Feb 7 22:48:32 2026
    From Newsgroup: comp.lang.c

    Bart <bc@freeuk.com> wrote:
    On 07/02/2026 18:07, Kaz Kylheku wrote:
    On 2026-02-06, Michael S <already5chosen@yahoo.com> wrote:
    On Fri, 6 Feb 2026 12:39:55 +0000
    Bart <bc@freeuk.com> wrote:

    On 06/02/2026 05:10, Keith Thompson wrote:
    Bart <bc@freeuk.com> writes:
    [...]
    /Some/ compilers with /some/ options will /sometimes/ tell you when >>>>>> you've got it wrong.

    But you first have to make an educated guess, or put in some dummy >>>>>> format code.

    Eventually, it will compile. Until someone else builds your
    program, using a slightly different set of headers where certain
    types are defined, and then it might either give compiler messages >>>>>> that they have to fix, or it show wrong results.

    That's not how I do it, and I don't think it's how most programmers
    do it.

    I know the rules well enough that I can usually write a correct
    format string in the first place. If I make a mistake, gcc's
    warnings are a nice check.

    I guess you've never used printf-family functions via the FFI of
    another language!



    Vararg via FFI? Is it really a good idea?

    I support it in TXR Lisp. However, it's limited in that the FFI
    definition is nailed to a particular choice of arguments.

    For instance we could make a function foo which takes two arguments:
    a str and an int, and calls the variadic printf.

    Then we can call (foo "%d" 42). It will call printf("%d", 42).

    We cannot pass fewer or more than two arguments to foo, and they have to
    be compatible with str and int.

    Demo:

    $ txr
    This is the TXR Lisp interactive listener of TXR 302.
    Quit with :quit or Ctrl-D on an empty line. Ctrl-X ? for cheatsheet.
    (with-dyn-lib nil
    (deffi printf-int "printf" int (str : int)))
    printf-int
    (printf-int "%d\n" 42)
    42
    3

    42 is output; 3 is the result value (3 characters output).

    The : syntax in the deffi macro call indicates the variadic list.
    It's not the case that we can make a variadic Lisp function pass its arguments
    as an arbitrarily long variadic list with arbitrary types to the wrapped FFI >> function. Fixed parameters must be declared after the colon.

    There's another issue with calling variadic functions, unrelated to the number of args. I can't tell from the above whether it's convered.

    Normally an arg that is passed in a register, will be passed in GPR for integer, or a floating point register if not.

    But a variadic float argument has to be passed in both, so for Win64
    ABI/x64 it might be in both rcx and xmm1. I think it is similar on SYS V
    for both x64 and arm64 (maybe on the latter both are passed in the GPR;
    I'd have to go and look it up).

    In SYS V convention argument is passed in exactly one place. It may
    be GPR, may be XMM register, may be on the stack. If you put right
    thing in RAX, then your arguments are valid regardless if the function
    is a vararg function or not.

    A dynamic treatment could be arranged via a heavy weight wrapper mechanism which
    dynamically analyzes the actual arguments, builds a libffi function descriptor
    on the fly, then uses it to make the call; it could be wortwhile for someone,
    but I didn't implement such a thing. Metaprogramming tricks revolving around
    dynamically evaluating deffi are also possible.

    My LIBFFI approach just uses assembly; it's the simplest way to do it.
    (The LIBFFI 'C' library also uses assembly to do the tricky bits.)

    There, for Win64 ABI, I found it easiest to just load all the register
    args to both integer and float registers, whether the called function
    was variadic or not. That's far more efficient than figuring out the
    right register argument by argument.

    I haven't implemented that for SYS V; that's more of a nightmare ABI
    where up to 6-12 args (8-16 on aarch64) can be passed between int and
    float registers depending on the mix of types.

    On Win64 ABI, it is 4 args, always.

    My code works fine for SYS V on amd64 and arm32. I do not think FFI
    for aarch64 will be any harder, but ATM I do not have code generator
    for aarch64, no need for FFI there.

    I did not bother with Windows, since I do not use it it would be
    untested and hence buggy code anyway.
    --
    Waldek Hebisch
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Bart@bc@freeuk.com to comp.lang.c on Sat Feb 7 23:54:18 2026
    From Newsgroup: comp.lang.c

    On 07/02/2026 22:48, Waldek Hebisch wrote:
    Bart <bc@freeuk.com> wrote:
    On 07/02/2026 18:07, Kaz Kylheku wrote:

    The : syntax in the deffi macro call indicates the variadic list.
    It's not the case that we can make a variadic Lisp function pass its arguments
    as an arbitrarily long variadic list with arbitrary types to the wrapped FFI
    function. Fixed parameters must be declared after the colon.

    There's another issue with calling variadic functions, unrelated to the
    number of args. I can't tell from the above whether it's convered.

    Normally an arg that is passed in a register, will be passed in GPR for
    integer, or a floating point register if not.

    But a variadic float argument has to be passed in both, so for Win64
    ABI/x64 it might be in both rcx and xmm1. I think it is similar on SYS V
    for both x64 and arm64 (maybe on the latter both are passed in the GPR;
    I'd have to go and look it up).

    In SYS V convention argument is passed in exactly one place. It may
    be GPR, may be XMM register, may be on the stack. If you put right
    thing in RAX, then your arguments are valid regardless if the function
    is a vararg function or not.

    I had to go and check this, and you're right. SYS V does nothing special
    when calling variadic functions.

    I guess that makes implementing the body of variadic functions harder,
    since it doesn't know where to look for the n'th variadic argument
    unless it knows the type.

    And even then, because the int and non-int args are spilled to separate blocks, it has to keep track of where the next arg is in which block.

    I think MS made the better call here; the necessary code is trivial for
    Win64 ABI.

    A dynamic treatment could be arranged via a heavy weight wrapper mechanism which
    dynamically analyzes the actual arguments, builds a libffi function descriptor
    on the fly, then uses it to make the call; it could be wortwhile for someone,
    but I didn't implement such a thing. Metaprogramming tricks revolving around
    dynamically evaluating deffi are also possible.

    My LIBFFI approach just uses assembly; it's the simplest way to do it.
    (The LIBFFI 'C' library also uses assembly to do the tricky bits.)

    There, for Win64 ABI, I found it easiest to just load all the register
    args to both integer and float registers, whether the called function
    was variadic or not. That's far more efficient than figuring out the
    right register argument by argument.

    I haven't implemented that for SYS V; that's more of a nightmare ABI
    where up to 6-12 args

    (Actually, 6-14 args; 6 max in GPRs and 8 in xmm regs)

    (8-16 on aarch64) can be passed between int and
    float registers depending on the mix of types.

    On Win64 ABI, it is 4 args, always.

    My code works fine for SYS V on amd64 and arm32. I do not think FFI
    for aarch64 will be any harder, but ATM I do not have code generator
    for aarch64, no need for FFI there.

    I did not bother with Windows, since I do not use it it would be
    untested and hence buggy code anyway.

    I started generating code for ARM64, but gave up because it was too hard
    and not fun (the RISC processor turned out to be a LOT more complex than
    the CISC x64!).

    The last straw was precisely to do with the SYS V call-conventions, and
    I hadn't even gotten to variadic arguments yet, nor to structs passed by-value, where the rules are labyrinthine.

    (I understand that neither LLVM or Cranelist backends support them
    directly; they need to be dealt with earlier on, so the user of those
    IRs needs to figure it out.)

    and hence buggy code anyway.

    I think you'd find it much simpler.



    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Michael S@already5chosen@yahoo.com to comp.lang.c on Sun Feb 8 10:52:46 2026
    From Newsgroup: comp.lang.c

    On Fri, 6 Feb 2026 13:04:34 +0000
    Bart <bc@freeuk.com> wrote:

    On 06/02/2026 12:47, Michael S wrote:
    On Fri, 6 Feb 2026 12:39:55 +0000
    Bart <bc@freeuk.com> wrote:

    On 06/02/2026 05:10, Keith Thompson wrote:
    Bart <bc@freeuk.com> writes:
    [...]
    /Some/ compilers with /some/ options will /sometimes/ tell you
    when you've got it wrong.

    But you first have to make an educated guess, or put in some
    dummy format code.

    Eventually, it will compile. Until someone else builds your
    program, using a slightly different set of headers where certain
    types are defined, and then it might either give compiler
    messages that they have to fix, or it show wrong results.

    That's not how I do it, and I don't think it's how most
    programmers do it.

    I know the rules well enough that I can usually write a correct
    format string in the first place. If I make a mistake, gcc's
    warnings are a nice check.

    I guess you've never used printf-family functions via the FFI of
    another language!



    Vararg via FFI? Is it really a good idea?


    It's covered by platform ABIs of both Windows and SYS V.


    My question was not about technical possibility. I understand that with
    enough of effort everything is possible.
    I asked whether it is a good idea.
    Is not it simpler for you and for your potential users to declare that
    your language can not call external C functions with variable number of arguments? To me it does not sound like this ability is either necessary
    or very valuable.

    Above I assume that we are talking about your scripting language.

    W.r.t. your other language, I have no strong opinion. But my weak
    opinion is that it also does not need it, possibly with exception for
    ability to do few (very few, hopefully) historically idiotically
    defined Unix system calls that can be handled individually as special
    cases.

    This is printf called from an interpreted language with dynamic
    typing:

    a := 12345678987654321
    b := pi
    c := "A"*10
    d := &a
    printf("%lld %f %s %p\n", a, b, c, d)

    Output is:

    12345678987654321 3.141593 AAAAAAAAAA 00000000036A1D48

    (Strings are converted to zero-terminated form for the FFI.)

    If I try it like this however:

    printf("%lld %f %s %p\n", d, c, b, a)

    It will go wrong (crashing inside the C library). With the built-in
    print feature, that doesn't happen:

    println a, b, c, d
    println d, c, b, a




    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Janis Papanagnou@janis_papanagnou+ng@hotmail.com to comp.lang.c on Sun Feb 8 12:18:20 2026
    From Newsgroup: comp.lang.c

    On 2026-02-08 09:52, Michael S wrote:
    On Fri, 6 Feb 2026 13:04:34 +0000
    Bart <bc@freeuk.com> wrote:
    On 06/02/2026 12:47, Michael S wrote:
    On Fri, 6 Feb 2026 12:39:55 +0000
    Bart <bc@freeuk.com> wrote:

    I guess you've never used printf-family functions via the FFI of
    another language!

    Vararg via FFI? Is it really a good idea?

    (This was exactly my thought.)

    (There's obviously a reason why languages that access "C" functions
    seem to avoid support for "varargs".)

    [...]
    [...]
    I asked whether it is a good idea.
    Is not it simpler for you and for your potential users to declare that
    your language can not call external C functions with variable number of arguments? To me it does not sound like this ability is either necessary
    or very valuable.

    C's "varargs" mechanism always appeared kludgy to me. Though I haven't
    followed "varargs" in "C" since K&R times, but I've a faint impression
    that something has been done and changed since back then.
    What was it, or is it still supported [only] in its original form?

    Janis

    [...]

    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Sun Feb 8 04:03:23 2026
    From Newsgroup: comp.lang.c

    Janis Papanagnou <janis_papanagnou+ng@hotmail.com> writes:
    [...]
    C's "varargs" mechanism always appeared kludgy to me. Though I
    haven't followed "varargs" in "C" since K&R times, but I've a
    faint impression that something has been done and changed since
    back then. What was it, or is it still supported [only] in its
    original form?

    Pre-ANSI C had a <varargs.h> header, used to process arguments to
    variadic functions like printf. C89 replaced it with <stdarg.h>,
    which hasn't changed much since then. Consult any recent standard
    draft for details. C23 removes the requirement for the last
    non-variadic argument to be passed to the va_start macro, allowing
    for functions with *only* variadic parameters.
    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From antispam@antispam@fricas.org (Waldek Hebisch) to comp.lang.c on Sun Feb 8 16:50:22 2026
    From Newsgroup: comp.lang.c

    Michael S <already5chosen@yahoo.com> wrote:
    On Fri, 6 Feb 2026 13:04:34 +0000
    Bart <bc@freeuk.com> wrote:

    On 06/02/2026 12:47, Michael S wrote:
    On Fri, 6 Feb 2026 12:39:55 +0000
    Bart <bc@freeuk.com> wrote:

    On 06/02/2026 05:10, Keith Thompson wrote:
    Bart <bc@freeuk.com> writes:
    [...]
    /Some/ compilers with /some/ options will /sometimes/ tell you
    when you've got it wrong.

    But you first have to make an educated guess, or put in some
    dummy format code.

    Eventually, it will compile. Until someone else builds your
    program, using a slightly different set of headers where certain
    types are defined, and then it might either give compiler
    messages that they have to fix, or it show wrong results.

    That's not how I do it, and I don't think it's how most
    programmers do it.

    I know the rules well enough that I can usually write a correct
    format string in the first place. If I make a mistake, gcc's
    warnings are a nice check.

    I guess you've never used printf-family functions via the FFI of
    another language!



    Vararg via FFI? Is it really a good idea?


    It's covered by platform ABIs of both Windows and SYS V.


    My question was not about technical possibility. I understand that with enough of effort everything is possible.
    I asked whether it is a good idea.
    Is not it simpler for you and for your potential users to declare that
    your language can not call external C functions with variable number of arguments? To me it does not sound like this ability is either necessary
    or very valuable.

    Above I assume that we are talking about your scripting language.

    W.r.t. your other language, I have no strong opinion. But my weak
    opinion is that it also does not need it, possibly with exception for
    ability to do few (very few, hopefully) historically idiotically
    defined Unix system calls that can be handled individually as special
    cases.

    Well, some important interfaces depend on varargs functions. And
    while you may handle such cases via user-written wrappers, it is
    much nicer if FFI machinery handles varargs.
    --
    Waldek Hebisch
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Sun Feb 8 18:55:32 2026
    From Newsgroup: comp.lang.c

    On 08/02/2026 17:50, Waldek Hebisch wrote:
    Michael S <already5chosen@yahoo.com> wrote:
    On Fri, 6 Feb 2026 13:04:34 +0000
    Bart <bc@freeuk.com> wrote:

    On 06/02/2026 12:47, Michael S wrote:
    On Fri, 6 Feb 2026 12:39:55 +0000
    Bart <bc@freeuk.com> wrote:

    On 06/02/2026 05:10, Keith Thompson wrote:
    Bart <bc@freeuk.com> writes:
    [...]
    /Some/ compilers with /some/ options will /sometimes/ tell you
    when you've got it wrong.

    But you first have to make an educated guess, or put in some
    dummy format code.

    Eventually, it will compile. Until someone else builds your
    program, using a slightly different set of headers where certain >>>>>>> types are defined, and then it might either give compiler
    messages that they have to fix, or it show wrong results.

    That's not how I do it, and I don't think it's how most
    programmers do it.

    I know the rules well enough that I can usually write a correct
    format string in the first place. If I make a mistake, gcc's
    warnings are a nice check.

    I guess you've never used printf-family functions via the FFI of
    another language!



    Vararg via FFI? Is it really a good idea?


    It's covered by platform ABIs of both Windows and SYS V.


    My question was not about technical possibility. I understand that with
    enough of effort everything is possible.
    I asked whether it is a good idea.
    Is not it simpler for you and for your potential users to declare that
    your language can not call external C functions with variable number of
    arguments? To me it does not sound like this ability is either necessary
    or very valuable.

    Above I assume that we are talking about your scripting language.

    W.r.t. your other language, I have no strong opinion. But my weak
    opinion is that it also does not need it, possibly with exception for
    ability to do few (very few, hopefully) historically idiotically
    defined Unix system calls that can be handled individually as special
    cases.

    Well, some important interfaces depend on varargs functions. And
    while you may handle such cases via user-written wrappers, it is
    much nicer if FFI machinery handles varargs.


    There are only a few standard C functions that are variadic, but they
    are quite important ones (with the *printf family at the top). But
    there is little doubt that C's handling of variadic functions is very
    unsafe, and implementations are often challenging (the SysV ABI for
    x86-64 is awkward and complex for variadic functions - the Win64 ABI is
    easier for variadics at the expense of making many other function calls
    less efficient). It is a weak point in the design of C.

    And for other languages trying to access C code via a FFI, variadic
    functions are going to be inefficient, complicated, and unsafe (unless
    you are /very/ sure of the calling parameters). If your alternative
    language is safer, neater, or easier to write than C (and if it isn't,
    why bother with it?), you'll want a better way to handle formatted
    printing than C's printf.


    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From antispam@antispam@fricas.org (Waldek Hebisch) to comp.lang.c on Sun Feb 8 19:21:41 2026
    From Newsgroup: comp.lang.c

    Bart <bc@freeuk.com> wrote:
    On 07/02/2026 22:48, Waldek Hebisch wrote:
    Bart <bc@freeuk.com> wrote:
    On 07/02/2026 18:07, Kaz Kylheku wrote:

    The : syntax in the deffi macro call indicates the variadic list.
    It's not the case that we can make a variadic Lisp function pass its arguments
    as an arbitrarily long variadic list with arbitrary types to the wrapped FFI
    function. Fixed parameters must be declared after the colon.

    There's another issue with calling variadic functions, unrelated to the
    number of args. I can't tell from the above whether it's convered.

    Normally an arg that is passed in a register, will be passed in GPR for
    integer, or a floating point register if not.

    But a variadic float argument has to be passed in both, so for Win64
    ABI/x64 it might be in both rcx and xmm1. I think it is similar on SYS V >>> for both x64 and arm64 (maybe on the latter both are passed in the GPR;
    I'd have to go and look it up).

    In SYS V convention argument is passed in exactly one place. It may
    be GPR, may be XMM register, may be on the stack. If you put right
    thing in RAX, then your arguments are valid regardless if the function
    is a vararg function or not.

    I had to go and check this, and you're right. SYS V does nothing special when calling variadic functions.

    Well, there is special thing: RAX should contain number of SSE
    registers used for passing parameters. You do not need to set
    RAX for normal calls (at least on Linux, some other systems
    require it for all calls).

    I guess that makes implementing the body of variadic functions harder,
    since it doesn't know where to look for the n'th variadic argument
    unless it knows the type.

    Well, if a function wants to do actual computation with an argument
    it should better know its type. So, this affects only "intermediate"
    functions that want to repack/shuffle arguments before passing them
    to some other function. That happens, but is reasonably rare.
    In one such case I just decided that intermediate function will
    work only for arguments passed in integer registers (this covered
    the actual use case). Note that regardless of types there is still
    problem of number of arguments. In my case the intermediate function
    just grabs 20 arguments and passes them further. Of course this
    is undefined behaviour in C, but in practice the function just
    gets garbage for non-existing arguments. The final recipient knows
    how many arguments were really passed and ignores extra garbage.

    And even then, because the int and non-int args are spilled to separate blocks, it has to keep track of where the next arg is in which block.

    I think MS made the better call here; the necessary code is trivial for Win64 ABI.

    A dynamic treatment could be arranged via a heavy weight wrapper mechanism which
    dynamically analyzes the actual arguments, builds a libffi function descriptor
    on the fly, then uses it to make the call; it could be wortwhile for someone,
    but I didn't implement such a thing. Metaprogramming tricks revolving around
    dynamically evaluating deffi are also possible.

    My LIBFFI approach just uses assembly; it's the simplest way to do it.
    (The LIBFFI 'C' library also uses assembly to do the tricky bits.)

    There, for Win64 ABI, I found it easiest to just load all the register
    args to both integer and float registers, whether the called function
    was variadic or not. That's far more efficient than figuring out the
    right register argument by argument.

    I haven't implemented that for SYS V; that's more of a nightmare ABI
    where up to 6-12 args

    (Actually, 6-14 args; 6 max in GPRs and 8 in xmm regs)

    (8-16 on aarch64) can be passed between int and
    float registers depending on the mix of types.

    On Win64 ABI, it is 4 args, always.

    My code works fine for SYS V on amd64 and arm32. I do not think FFI
    for aarch64 will be any harder, but ATM I do not have code generator
    for aarch64, no need for FFI there.

    I did not bother with Windows, since I do not use it it would be
    untested and hence buggy code anyway.

    I started generating code for ARM64, but gave up because it was too hard
    and not fun (the RISC processor turned out to be a LOT more complex than
    the CISC x64!).

    Well, RISC processor means that compiler have to do work which is
    frequently done by hardware on a CISC. Concerning arm32, most
    annoying for me was limited range of constants, especially limit
    on offsets that can be part of an instruction. With my current
    implementation that puts something like 2kB limit on size of local
    variables. And my generator mixes instructions and constant data
    (otherwise it could not access constant data using limited available
    offsets), which works but compilcates code generator and probably
    gives suboptimal performance.

    The last straw was precisely to do with the SYS V call-conventions, and
    I hadn't even gotten to variadic arguments yet, nor to structs passed by-value, where the rules are labyrinthine.

    My low-level code only handles scalar arguments. That includes pointer
    to structures, but not structures passed by value. Structures passed by
    value could be handled by higher-level code, but up to now there was
    no need to do this.

    BTW, my amd64 code is assembler, so off-topic here, but arm32 code
    is mostly C. I use two helper structures:

    struct registers_buffer {
    int i_reg[4];
    union {double d; struct {float sl; float sh;} sf2;} f_reg[8];
    };

    typedef struct registers_buffer reg_buff;

    typedef struct arg_state { int ni; int sfi; int dfi; int si;} arg_state;

    C code fills 'reg_buff' with values and later low-level assembly
    copies values from the buffer to registers. I allocate enough space on
    the stack so that C code can write to the stack without risk of
    stack overflow.

    There are 3 helper routines:

    static void
    store_single(arg_state * as, void * sp, reg_buff * rp, float val) {
    float * dst;
    int sfi = as->sfi;
    if (sfi < 16) {
    int dfi1 = sfi >> 1;
    if (sfi & 1) {
    dst = &(rp->f_reg[dfi1].sf2.sh);
    as->sfi = (as->dfi)<<1;
    } else {
    dst = &(rp->f_reg[dfi1].sf2.sl);
    as->sfi++;
    as->dfi = (as->dfi > dfi1)?as->dfi:(dfi1 + 1);
    }
    } else {
    dst = (float *)sp + as->si;
    as->si++;
    }
    memcpy(dst, &val, sizeof(val));
    }

    static void
    store_double(arg_state * as, void * sp, reg_buff * rp, double val) {
    int dfi = as->dfi;
    double * dst;
    if (dfi < 8) {
    dst = &(rp->f_reg[dfi].d);
    as->dfi++;
    if (as->sfi == (dfi<<1)) {
    as->sfi = (as->dfi<<1);
    }
    } else {
    int si = as->si;
    as->sfi = 16;
    si = (si + 1)&(~1);
    dst = (double *)sp + (si>>1);
    as->si = si + 2;
    }
    memcpy(dst, &val, sizeof(val));
    }

    static void
    store_int(arg_state * as, void * sp, reg_buff * rp, int val) {
    int ni = as->ni;
    if (ni < 4) {
    rp->i_reg[ni] = val;
    as->ni++;
    } else {
    *((int *)sp + as->si) = val;
    as->si++;
    }
    }

    There is main loop that goes over argument and calls 'store_int', 'store_double' or 'store_single' as apropriate (actual determiantion
    of types is specific to my program, so I skip this part, the above
    in principle is reusable).

    (I understand that neither LLVM or Cranelist backends support them
    directly; they need to be dealt with earlier on, so the user of those
    IRs needs to figure it out.)

    and hence buggy code anyway.

    I think you'd find it much simpler.



    --
    Waldek Hebisch
    PR_AS;
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Bart@bc@freeuk.com to comp.lang.c on Sun Feb 8 19:43:01 2026
    From Newsgroup: comp.lang.c

    On 08/02/2026 08:52, Michael S wrote:
    On Fri, 6 Feb 2026 13:04:34 +0000
    Bart <bc@freeuk.com> wrote:


    Vararg via FFI? Is it really a good idea?


    It's covered by platform ABIs of both Windows and SYS V.


    My question was not about technical possibility. I understand that with enough of effort everything is possible.
    I asked whether it is a good idea.
    Is not it simpler for you and for your potential users to declare that
    your language can not call external C functions with variable number of arguments? To me it does not sound like this ability is either necessary
    or very valuable.

    Above I assume that we are talking about your scripting language.

    W.r.t. your other language, I have no strong opinion. But my weak
    opinion is that it also does not need it, possibly with exception for
    ability to do few (very few, hopefully) historically idiotically
    defined Unix system calls that can be handled individually as special
    cases.


    It's needed if any program in either of my languages needs to call an
    FFI function in a library that happens to use variadic arguments.

    I already know that at least one language needs to call *printf with at
    least two arguments.

    But it can come up elsewhere. Looking around:

    extern DECLSPEC void SDLCALL SDL_Log(SDL_PRINTF_FORMAT_STRING const
    char *fmt, ...) SDL_PRINTF_VARARG_FUNC(1);
    SQLITE_API int sqlite3_config(int, ...); // many like this
    SQLITE_PRIVATE void sqlite3VdbeMultiLoad(Vdbe*,int,const char*,...);
    LUA_API int (lua_gc) (lua_State *L, int what, ...);
    int *elem(array a, ...){
    static void error(const char *msg, ...)
    int open(const char* filename, int flags, ...)
    int wsprintfW(LPWSTR,LPCWSTR,...);

    Mostly likely variadic functions were only created in order to implement 'print', but nothing stops people using them for other purposes.

    So the likelihood is there for them to occur in APIs.

    In my case, most support I have to do is within my backend, and the same backend is needed for my C compiler. So it is needed anyway.



    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Bart@bc@freeuk.com to comp.lang.c on Sun Feb 8 19:54:27 2026
    From Newsgroup: comp.lang.c

    On 08/02/2026 17:55, David Brown wrote:
    On 08/02/2026 17:50, Waldek Hebisch wrote:
    Michael S <already5chosen@yahoo.com> wrote:
    On Fri, 6 Feb 2026 13:04:34 +0000
    Bart <bc@freeuk.com> wrote:

    On 06/02/2026 12:47, Michael S wrote:
    On Fri, 6 Feb 2026 12:39:55 +0000
    Bart <bc@freeuk.com> wrote:
    On 06/02/2026 05:10, Keith Thompson wrote:
    Bart <bc@freeuk.com> writes:
    [...]
    /Some/ compilers with /some/ options will /sometimes/ tell you >>>>>>>> when you've got it wrong.

    But you first have to make an educated guess, or put in some
    dummy format code.

    Eventually, it will compile. Until someone else builds your
    program, using a slightly different set of headers where certain >>>>>>>> types are defined, and then it might either give compiler
    messages that they have to fix, or it show wrong results.

    That's not how I do it, and I don't think it's how most
    programmers do it.

    I know the rules well enough that I can usually write a correct
    format string in the first place.  If I make a mistake, gcc's
    warnings are a nice check.

    I guess you've never used printf-family functions via the FFI of
    another language!


    Vararg via FFI? Is it really a good idea?

    It's covered by platform ABIs of both Windows and SYS V.


    My question was not about technical possibility. I understand that with
    enough of effort everything is possible.
    I asked whether it is a good idea.
    Is not it simpler for you and for your potential users to declare that
    your language can not call external C functions with variable number of
    arguments? To me it does not sound like this ability is either necessary >>> or very valuable.

    Above I assume that we are talking about your scripting language.

    W.r.t. your other language, I have no strong opinion. But my weak
    opinion is that it also does not need it, possibly with exception for
    ability to do few (very few, hopefully) historically idiotically
    defined Unix system calls that can be handled individually as special
    cases.

    Well, some important interfaces depend on varargs functions.  And
    while you may handle such cases via user-written wrappers, it is
    much nicer if FFI machinery handles varargs.


    There are only a few standard C functions that are variadic, but they
    are quite important ones (with the *printf family at the top).  But
    there is little doubt that C's handling of variadic functions is very unsafe, and implementations are often challenging (the SysV ABI for
    x86-64 is awkward and complex for variadic functions - the Win64 ABI is easier for variadics at the expense of making many other function calls
    less efficient).  It is a weak point in the design of C.

    And for other languages trying to access C code via a FFI, variadic functions are going to be inefficient, complicated, and unsafe (unless
    you are /very/ sure of the calling parameters).  If your alternative language is safer, neater, or easier to write than C (and if it isn't,
    why bother with it?), you'll want a better way to handle formatted
    printing than C's printf.

    For about the last 30 years I've used the C library to do i/o, as it was simpler than Win32 calls. (I didn't even know it was supposed to be for
    C; it just look like an easier library with shorter function names, that
    also shipped with Windows.)

    So while I do do most of my own conversion and formating, actual i/o,
    and a few cases such as floating point which are fiddly, uses calls like
    this:

    sprintf(s, "%f", x)
    sscanf(str, "%lf%n", &x, &numlength)
    printf("%.*s", length, s)
    fprintf(f, "%.*s", length, s)

    The last two are generally called when I have a buffer-full of output.

    The sscanf call is the only I ever time use *scanf functions.

    In the prior 15 years of course, I did have to do everything.



    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From antispam@antispam@fricas.org (Waldek Hebisch) to comp.lang.c on Sun Feb 8 19:59:01 2026
    From Newsgroup: comp.lang.c

    David Brown <david.brown@hesbynett.no> wrote:
    On 08/02/2026 17:50, Waldek Hebisch wrote:
    Michael S <already5chosen@yahoo.com> wrote:
    On Fri, 6 Feb 2026 13:04:34 +0000
    Bart <bc@freeuk.com> wrote:

    On 06/02/2026 12:47, Michael S wrote:
    On Fri, 6 Feb 2026 12:39:55 +0000
    Bart <bc@freeuk.com> wrote:

    On 06/02/2026 05:10, Keith Thompson wrote:
    Bart <bc@freeuk.com> writes:
    [...]
    /Some/ compilers with /some/ options will /sometimes/ tell you >>>>>>>> when you've got it wrong.

    But you first have to make an educated guess, or put in some
    dummy format code.

    Eventually, it will compile. Until someone else builds your
    program, using a slightly different set of headers where certain >>>>>>>> types are defined, and then it might either give compiler
    messages that they have to fix, or it show wrong results.

    That's not how I do it, and I don't think it's how most
    programmers do it.

    I know the rules well enough that I can usually write a correct
    format string in the first place. If I make a mistake, gcc's
    warnings are a nice check.

    I guess you've never used printf-family functions via the FFI of
    another language!



    Vararg via FFI? Is it really a good idea?


    It's covered by platform ABIs of both Windows and SYS V.


    My question was not about technical possibility. I understand that with
    enough of effort everything is possible.
    I asked whether it is a good idea.
    Is not it simpler for you and for your potential users to declare that
    your language can not call external C functions with variable number of
    arguments? To me it does not sound like this ability is either necessary >>> or very valuable.

    Above I assume that we are talking about your scripting language.

    W.r.t. your other language, I have no strong opinion. But my weak
    opinion is that it also does not need it, possibly with exception for
    ability to do few (very few, hopefully) historically idiotically
    defined Unix system calls that can be handled individually as special
    cases.

    Well, some important interfaces depend on varargs functions. And
    while you may handle such cases via user-written wrappers, it is
    much nicer if FFI machinery handles varargs.


    There are only a few standard C functions that are variadic, but they
    are quite important ones (with the *printf family at the top). But
    there is little doubt that C's handling of variadic functions is very unsafe, and implementations are often challenging (the SysV ABI for
    x86-64 is awkward and complex for variadic functions - the Win64 ABI is easier for variadics at the expense of making many other function calls
    less efficient). It is a weak point in the design of C.

    And for other languages trying to access C code via a FFI, variadic functions are going to be inefficient, complicated, and unsafe (unless
    you are /very/ sure of the calling parameters). If your alternative language is safer, neater, or easier to write than C (and if it isn't,
    why bother with it?), you'll want a better way to handle formatted
    printing than C's printf.

    For me 'printf' is mainly a debugging helper. Example of "important
    interface" is X11. Part of that interface is variadic and there is
    no way around this. Concerning safety, when using FFI there is
    always problem of "passing" type information across the interface.
    Smaller or bigger part of type information is provide by hand,
    which in inherently unsafe. The point is to minimize and
    encapsulate this part so users can assume that it just works.
    Variadic functions decrease safety, but IMO this is just modest
    decrease compared to "normal" troubls with FFI.

    Concerning "inefficient", in cases interesting to me FFI is
    inefficient and variadic functions do not change this much. Simply,
    foreign calls are for serious, relatively expensive tasks.

    And to make things clear: I do not advocate writing variadic
    functions. But there are already libraries exporting variadic
    and FFI should allow using them.
    --
    Waldek Hebisch
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Sun Feb 8 21:27:02 2026
    From Newsgroup: comp.lang.c

    On 08/02/2026 20:54, Bart wrote:
    On 08/02/2026 17:55, David Brown wrote:
    On 08/02/2026 17:50, Waldek Hebisch wrote:
    Michael S <already5chosen@yahoo.com> wrote:
    On Fri, 6 Feb 2026 13:04:34 +0000
    Bart <bc@freeuk.com> wrote:

    On 06/02/2026 12:47, Michael S wrote:
    On Fri, 6 Feb 2026 12:39:55 +0000
    Bart <bc@freeuk.com> wrote:
    On 06/02/2026 05:10, Keith Thompson wrote:
    Bart <bc@freeuk.com> writes:
    [...]
    /Some/ compilers with /some/ options will /sometimes/ tell you >>>>>>>>> when you've got it wrong.

    But you first have to make an educated guess, or put in some >>>>>>>>> dummy format code.

    Eventually, it will compile. Until someone else builds your
    program, using a slightly different set of headers where certain >>>>>>>>> types are defined, and then it might either give compiler
    messages that they have to fix, or it show wrong results.

    That's not how I do it, and I don't think it's how most
    programmers do it.

    I know the rules well enough that I can usually write a correct >>>>>>>> format string in the first place.  If I make a mistake, gcc's >>>>>>>> warnings are a nice check.

    I guess you've never used printf-family functions via the FFI of >>>>>>> another language!


    Vararg via FFI? Is it really a good idea?

    It's covered by platform ABIs of both Windows and SYS V.


    My question was not about technical possibility. I understand that with >>>> enough of effort everything is possible.
    I asked whether it is a good idea.
    Is not it simpler for you and for your potential users to declare that >>>> your language can not call external C functions with variable number of >>>> arguments? To me it does not sound like this ability is either
    necessary
    or very valuable.

    Above I assume that we are talking about your scripting language.

    W.r.t. your other language, I have no strong opinion. But my weak
    opinion is that it also does not need it, possibly with exception for
    ability to do few (very few, hopefully) historically idiotically
    defined Unix system calls that can be handled individually as special
    cases.

    Well, some important interfaces depend on varargs functions.  And
    while you may handle such cases via user-written wrappers, it is
    much nicer if FFI machinery handles varargs.


    There are only a few standard C functions that are variadic, but they
    are quite important ones (with the *printf family at the top).  But
    there is little doubt that C's handling of variadic functions is very
    unsafe, and implementations are often challenging (the SysV ABI for
    x86-64 is awkward and complex for variadic functions - the Win64 ABI
    is easier for variadics at the expense of making many other function
    calls less efficient).  It is a weak point in the design of C.

    And for other languages trying to access C code via a FFI, variadic
    functions are going to be inefficient, complicated, and unsafe (unless
    you are /very/ sure of the calling parameters).  If your alternative
    language is safer, neater, or easier to write than C (and if it isn't,
    why bother with it?), you'll want a better way to handle formatted
    printing than C's printf.

    For about the last 30 years I've used the C library to do i/o, as it was simpler than Win32 calls. (I didn't even know it was supposed to be for
    C; it just look like an easier library with shorter function names, that also shipped with Windows.)

    So while I do do most of my own conversion and formating, actual i/o,
    and a few cases such as floating point which are fiddly, uses calls like this:

        sprintf(s, "%f", x)
        sscanf(str, "%lf%n", &x, &numlength)
        printf("%.*s", length, s)
        fprintf(f, "%.*s", length, s)

    The last two are generally called when I have a buffer-full of output.

    The sscanf call is the only I ever time use *scanf functions.

    In the prior 15 years of course, I did have to do everything.


    Doing your print's by building up a C string in your own
    language/runtime and then calling C's "printf("%s", p")" is safe and
    should be simple enough to implement.

    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Janis Papanagnou@janis_papanagnou+ng@hotmail.com to comp.lang.c on Sun Feb 8 21:51:01 2026
    From Newsgroup: comp.lang.c

    On 2026-02-08 18:55, David Brown wrote:
    On 08/02/2026 17:50, Waldek Hebisch wrote:

    Well, some important interfaces depend on varargs functions.  And
    while you may handle such cases via user-written wrappers, it is
    much nicer if FFI machinery handles varargs.

    There are only a few standard C functions that are variadic, but they
    are quite important ones (with the *printf family at the top).

    Hmm.. - if I'm thinking of using "C" standard library functions from
    other languages the 'printf' (or 'scanf') family would be the least
    I'd be thinking of; I/O can typically be found natively in languages,
    and these ("unsafe") "C" functions I'd not really consider a paragon
    to abstain from a direct, type-safe, safe, and efficient use of any
    native function. - So I'd not call these "C"-functions "important"
    (from the perspective of the respective native language).

    I was seeking for other variadic standard "C" functions but didn't
    find (m)any.

    Where do we need variadic functions? Functions like printf/scanf which
    are costly (due to the additional format interpretation), or (as found
    in other languages) things like min/max which are homogeneous in types
    and are certainly better represented by arrays.

    I seem to recall we used varargs in the early 1990's in context of a
    logging framework, but that was all. Were do folks here use them for?
    I'm really curious to hear where they're are actually used in projects.

    But
    there is little doubt that C's handling of variadic functions is very unsafe, and implementations are often challenging (the SysV ABI for
    x86-64 is awkward and complex for variadic functions - the Win64 ABI is easier for variadics at the expense of making many other function calls
    less efficient).  It is a weak point in the design of C.

    And for other languages trying to access C code via a FFI, variadic functions are going to be inefficient, complicated, and unsafe (unless
    you are /very/ sure of the calling parameters).  If your alternative language is safer, neater, or easier to write than C (and if it isn't,
    why bother with it?), you'll want a better way to handle formatted
    printing than C's printf.

    Indeed.

    Janis

    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Bart@bc@freeuk.com to comp.lang.c on Sun Feb 8 23:35:31 2026
    From Newsgroup: comp.lang.c

    On 08/02/2026 19:21, Waldek Hebisch wrote:
    Bart <bc@freeuk.com> wrote:
    On 07/02/2026 22:48, Waldek Hebisch wrote:

    In SYS V convention argument is passed in exactly one place. It may
    be GPR, may be XMM register, may be on the stack. If you put right
    thing in RAX, then your arguments are valid regardless if the function
    is a vararg function or not.

    I had to go and check this, and you're right. SYS V does nothing special
    when calling variadic functions.

    Well, there is special thing: RAX should contain number of SSE
    registers used for passing parameters. You do not need to set
    RAX for normal calls (at least on Linux, some other systems
    require it for all calls).

    I looked out for that but don't remember seeing in on godbolt.org, and I
    think it was for SYS V.

    But I tried it again, and AL is being set to some count, which appears
    to be the total number of float arguments (and rereading your comment,
    you say the same thing).


    I guess that makes implementing the body of variadic functions harder,
    since it doesn't know where to look for the n'th variadic argument
    unless it knows the type.

    Well, if a function wants to do actual computation with an argument
    it should better know its type.

    On Windows, it will know the location of the next vararg and can access
    its value before it knows the type. The user-provided type (eg.
    'var_arg(p, int)') can simple do a type-punning cast on the value.

    All args: fixed, variadic-reg, variadic-pushed, will also all be in consecutive stack slots, regardless of type (This is the real reason why floats should be loaded to GPRs for variadics: entry code just needs to
    spill those 4 GPRs, it anyway won't know the mix of types.)

    I started generating code for ARM64, but gave up because it was too hard
    and not fun (the RISC processor turned out to be a LOT more complex than
    the CISC x64!).

    Well, RISC processor means that compiler have to do work which is
    frequently done by hardware on a CISC. Concerning arm32, most
    annoying for me was limited range of constants, especially limit
    on offsets that can be part of an instruction. With my current implementation that puts something like 2kB limit on size of local
    variables. And my generator mixes instructions and constant data
    (otherwise it could not access constant data using limited available offsets), which works but compilcates code generator and probably
    gives suboptimal performance.

    There are a dozen annoying things like this on arm64. Even when you give
    up and decide to load 64-bit constants from a memory pool, you find you
    can't even directly access that pool as it has an absolute address. That
    can involve first loading the page address (ie. minus lower 12 bits) to
    R, then you have to use an address mode involving R and the lower 12
    bits as an offset.

    The last straw was precisely to do with the SYS V call-conventions, and
    I hadn't even gotten to variadic arguments yet, nor to structs passed
    by-value, where the rules are labyrinthine.

    My low-level code only handles scalar arguments. That includes pointer
    to structures, but not structures passed by value. Structures passed by value could be handled by higher-level code, but up to now there was
    no need to do this.

    BTW, my amd64 code is assembler, so off-topic here, but arm32 code
    is mostly C. I use two helper structures:

    struct registers_buffer {
    int i_reg[4];
    union {double d; struct {float sl; float sh;} sf2;} f_reg[8];
    };

    typedef struct registers_buffer reg_buff;

    typedef struct arg_state { int ni; int sfi; int dfi; int si;} arg_state;

    C code fills 'reg_buff' with values and later low-level assembly
    copies values from the buffer to registers. I allocate enough space on
    the stack so that C code can write to the stack without risk of
    stack overflow.

    There are 3 helper routines:


    This looks pretty complicated, but what is it for: is it still to do
    with variadic functions, or is to with the LIBFFI problem?

    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From antispam@antispam@fricas.org (Waldek Hebisch) to comp.lang.c on Mon Feb 9 01:27:43 2026
    From Newsgroup: comp.lang.c

    Bart <bc@freeuk.com> wrote:
    On 08/02/2026 19:21, Waldek Hebisch wrote:
    Bart <bc@freeuk.com> wrote:
    On 07/02/2026 22:48, Waldek Hebisch wrote:

    In SYS V convention argument is passed in exactly one place. It may
    be GPR, may be XMM register, may be on the stack. If you put right
    thing in RAX, then your arguments are valid regardless if the function >>>> is a vararg function or not.

    I had to go and check this, and you're right. SYS V does nothing special >>> when calling variadic functions.

    Well, there is special thing: RAX should contain number of SSE
    registers used for passing parameters. You do not need to set
    RAX for normal calls (at least on Linux, some other systems
    require it for all calls).

    I looked out for that but don't remember seeing in on godbolt.org, and I think it was for SYS V.

    But I tried it again, and AL is being set to some count, which appears
    to be the total number of float arguments (and rereading your comment,
    you say the same thing).


    I guess that makes implementing the body of variadic functions harder,
    since it doesn't know where to look for the n'th variadic argument
    unless it knows the type.

    Well, if a function wants to do actual computation with an argument
    it should better know its type.

    On Windows, it will know the location of the next vararg and can access
    its value before it knows the type. The user-provided type (eg.
    'var_arg(p, int)') can simple do a type-punning cast on the value.

    All args: fixed, variadic-reg, variadic-pushed, will also all be in consecutive stack slots, regardless of type (This is the real reason why floats should be loaded to GPRs for variadics: entry code just needs to spill those 4 GPRs, it anyway won't know the mix of types.)

    I started generating code for ARM64, but gave up because it was too hard >>> and not fun (the RISC processor turned out to be a LOT more complex than >>> the CISC x64!).

    Well, RISC processor means that compiler have to do work which is
    frequently done by hardware on a CISC. Concerning arm32, most
    annoying for me was limited range of constants, especially limit
    on offsets that can be part of an instruction. With my current
    implementation that puts something like 2kB limit on size of local
    variables. And my generator mixes instructions and constant data
    (otherwise it could not access constant data using limited available
    offsets), which works but compilcates code generator and probably
    gives suboptimal performance.

    There are a dozen annoying things like this on arm64. Even when you give
    up and decide to load 64-bit constants from a memory pool, you find you can't even directly access that pool as it has an absolute address. That
    can involve first loading the page address (ie. minus lower 12 bits) to
    R, then you have to use an address mode involving R and the lower 12
    bits as an offset.

    As I wrote I generate constant pool as part of instruction stream.
    I use PC-relative adressing so as long as constant is close
    enough to instruction using it I can use short offsets.
    There is some extra effort, normally I am trying to put constants
    after unconditional jump and before next label, but I may need
    extra jump to "jump around" constants.

    The last straw was precisely to do with the SYS V call-conventions, and
    I hadn't even gotten to variadic arguments yet, nor to structs passed
    by-value, where the rules are labyrinthine.

    My low-level code only handles scalar arguments. That includes pointer
    to structures, but not structures passed by value. Structures passed by
    value could be handled by higher-level code, but up to now there was
    no need to do this.

    BTW, my amd64 code is assembler, so off-topic here, but arm32 code
    is mostly C. I use two helper structures:

    struct registers_buffer {
    int i_reg[4];
    union {double d; struct {float sl; float sh;} sf2;} f_reg[8];
    };

    typedef struct registers_buffer reg_buff;

    typedef struct arg_state { int ni; int sfi; int dfi; int si;} arg_state;

    C code fills 'reg_buff' with values and later low-level assembly
    copies values from the buffer to registers. I allocate enough space on
    the stack so that C code can write to the stack without risk of
    stack overflow.

    There are 3 helper routines:


    This looks pretty complicated, but what is it for: is it still to do
    with variadic functions, or is to with the LIBFFI problem?

    This is to handle a call, it does not matter variadic or not.
    The call is from dynamically typed language and argument types are
    known only at runtime (actually, argument types _may_ be statically
    known at higher level, but for simplicity "all" (see below) calls go
    trough a single low-level routine that handles general dynamic case.
    Dispatcher routine (which I did not show) loops over arguments,
    decodes their types and converts them to C representation. Then
    it calls one of the 3 helper routines to place each argument in the buffer
    or on the stack.

    There is simpler integer only code which is mainly used to perform
    low level system calls. This iterface do not convert arguments
    (the assumption is that caller passes C-compatible representation)
    and logic is simpler as it just puts what fits in registers and
    the rest on the stack.

    Both interfaces spill all registers used by calling language to
    the stack before actual processing of the call. This is because
    C code may perform a callback and callback may trigger garbage
    collection and garbage collector needs to see all registers that
    may point to language data. There are global variables which
    tell garbage collector which parts of the stack are managed by
    the language (and need to be scanned) and which belong to C or
    FFI machinery (garbage collector ignores this part).
    --
    Waldek Hebisch
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From antispam@antispam@fricas.org (Waldek Hebisch) to comp.lang.c on Mon Feb 9 01:40:02 2026
    From Newsgroup: comp.lang.c

    Bart <bc@freeuk.com> wrote:
    On 07/02/2026 22:48, Waldek Hebisch wrote:

    BTW, my amd64 code is assembler, so off-topic here, but arm32 code
    is mostly C. I use two helper structures:

    struct registers_buffer {
    int i_reg[4];
    union {double d; struct {float sl; float sh;} sf2;} f_reg[8];
    };

    typedef struct registers_buffer reg_buff;

    typedef struct arg_state { int ni; int sfi; int dfi; int si;} arg_state;

    C code fills 'reg_buff' with values and later low-level assembly
    copies values from the buffer to registers. I allocate enough space on
    the stack so that C code can write to the stack without risk of
    stack overflow.

    There are 3 helper routines:


    This looks pretty complicated, but what is it for: is it still to do
    with variadic functions, or is to with the LIBFFI problem?

    Just little extra thing: the code I posted just copies arguments, but
    the same logic can be used at compile time to generate parameter
    passing code.

    Concerning "pretty complicated", it is doing what specification says
    it should do, it is shorter than the specification and IMO it is
    at least as readible as the specification (OK, for people that do not
    know the specification bunch of comments would be useful). Since
    it is C a few things that are not entirely clear in the specification
    are precisly specified.
    --
    Waldek Hebisch
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Bart@bc@freeuk.com to comp.lang.c on Tue Feb 10 00:14:02 2026
    From Newsgroup: comp.lang.c

    On 09/02/2026 01:27, Waldek Hebisch wrote:
    Bart <bc@freeuk.com> wrote:
    On 08/02/2026 19:21, Waldek Hebisch wrote:
    Bart <bc@freeuk.com> wrote:
    On 07/02/2026 22:48, Waldek Hebisch wrote:

    In SYS V convention argument is passed in exactly one place. It may >>>>> be GPR, may be XMM register, may be on the stack. If you put right
    thing in RAX, then your arguments are valid regardless if the function >>>>> is a vararg function or not.

    I had to go and check this, and you're right. SYS V does nothing special >>>> when calling variadic functions.

    Well, there is special thing: RAX should contain number of SSE
    registers used for passing parameters. You do not need to set
    RAX for normal calls (at least on Linux, some other systems
    require it for all calls).

    I looked out for that but don't remember seeing in on godbolt.org, and I
    think it was for SYS V.

    But I tried it again, and AL is being set to some count, which appears
    to be the total number of float arguments (and rereading your comment,
    you say the same thing).


    I guess that makes implementing the body of variadic functions harder, >>>> since it doesn't know where to look for the n'th variadic argument
    unless it knows the type.

    Well, if a function wants to do actual computation with an argument
    it should better know its type.

    On Windows, it will know the location of the next vararg and can access
    its value before it knows the type. The user-provided type (eg.
    'var_arg(p, int)') can simple do a type-punning cast on the value.

    All args: fixed, variadic-reg, variadic-pushed, will also all be in
    consecutive stack slots, regardless of type (This is the real reason why
    floats should be loaded to GPRs for variadics: entry code just needs to
    spill those 4 GPRs, it anyway won't know the mix of types.)

    I started generating code for ARM64, but gave up because it was too hard >>>> and not fun (the RISC processor turned out to be a LOT more complex than >>>> the CISC x64!).

    Well, RISC processor means that compiler have to do work which is
    frequently done by hardware on a CISC. Concerning arm32, most
    annoying for me was limited range of constants, especially limit
    on offsets that can be part of an instruction. With my current
    implementation that puts something like 2kB limit on size of local
    variables. And my generator mixes instructions and constant data
    (otherwise it could not access constant data using limited available
    offsets), which works but compilcates code generator and probably
    gives suboptimal performance.

    There are a dozen annoying things like this on arm64. Even when you give
    up and decide to load 64-bit constants from a memory pool, you find you
    can't even directly access that pool as it has an absolute address. That
    can involve first loading the page address (ie. minus lower 12 bits) to
    R, then you have to use an address mode involving R and the lower 12
    bits as an offset.

    As I wrote I generate constant pool as part of instruction stream.
    I use PC-relative adressing so as long as constant is close
    enough to instruction using it I can use short offsets.
    There is some extra effort, normally I am trying to put constants
    after unconditional jump and before next label, but I may need
    extra jump to "jump around" constants.

    The last straw was precisely to do with the SYS V call-conventions, and >>>> I hadn't even gotten to variadic arguments yet, nor to structs passed
    by-value, where the rules are labyrinthine.

    My low-level code only handles scalar arguments. That includes pointer
    to structures, but not structures passed by value. Structures passed by >>> value could be handled by higher-level code, but up to now there was
    no need to do this.

    BTW, my amd64 code is assembler, so off-topic here, but arm32 code
    is mostly C. I use two helper structures:

    struct registers_buffer {
    int i_reg[4];
    union {double d; struct {float sl; float sh;} sf2;} f_reg[8];
    };

    typedef struct registers_buffer reg_buff;

    typedef struct arg_state { int ni; int sfi; int dfi; int si;} arg_state; >>>
    C code fills 'reg_buff' with values and later low-level assembly
    copies values from the buffer to registers. I allocate enough space on
    the stack so that C code can write to the stack without risk of
    stack overflow.

    There are 3 helper routines:


    This looks pretty complicated, but what is it for: is it still to do
    with variadic functions, or is to with the LIBFFI problem?

    This is to handle a call, it does not matter variadic or not.
    The call is from dynamically typed language and argument types are
    known only at runtime

    OK, so it is probably performing the LIBFFI thing, or rather what needs
    to come before, which is to marshall the arguments into homogenous array
    of values. The actual LIBFFI task needs some ASM support as you say.

    In that case, my own code to do the same (not in C) is probably more fiddly:

    https://github.com/sal55/langs/blob/master/calldll.m

    The interpreter calls 'calldll()'. 'vartopacked()' does the translation
    from tagged dynamic values to suitable FFI types. Here the args are
    assembled into an i64 array.

    The actual call is done with 'os_calldllfunction' which uses inline
    assembly; that has been appended.

    I also have a version of that in pure HLL code, but it only handles the
    most common combinations. (I used that if transpiling to C.)


    (actually, argument types _may_ be statically
    known at higher level, but for simplicity "all" (see below) calls go
    trough a single low-level routine that handles general dynamic case. Dispatcher routine (which I did not show) loops over arguments,
    decodes their types and converts them to C representation. Then
    it calls one of the 3 helper routines to place each argument in the buffer
    or on the stack.

    There is simpler integer only code which is mainly used to perform
    low level system calls. This iterface do not convert arguments
    (the assumption is that caller passes C-compatible representation)
    and logic is simpler as it just puts what fits in registers and
    the rest on the stack.

    Both interfaces spill all registers used by calling language to
    the stack before actual processing of the call. This is because
    C code may perform a callback and callback may trigger garbage

    So here the dynamic code is not interpreted?

    That's a problem for me: I can't pass a the address of a callback
    function which only exists as bytecode!

    (For the purpose of running WinAPI GUI programs, I set up a special
    callback function within the compiler, and that then has to start a new interpreter instance briefly.)



    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From antispam@antispam@fricas.org (Waldek Hebisch) to comp.lang.c on Tue Feb 10 13:22:03 2026
    From Newsgroup: comp.lang.c

    Bart <bc@freeuk.com> wrote:
    On 09/02/2026 01:27, Waldek Hebisch wrote:
    Bart <bc@freeuk.com> wrote:
    On 08/02/2026 19:21, Waldek Hebisch wrote:
    Bart <bc@freeuk.com> wrote:
    On 07/02/2026 22:48, Waldek Hebisch wrote:

    The last straw was precisely to do with the SYS V call-conventions, and >>>>> I hadn't even gotten to variadic arguments yet, nor to structs passed >>>>> by-value, where the rules are labyrinthine.

    My low-level code only handles scalar arguments. That includes pointer >>>> to structures, but not structures passed by value. Structures passed by >>>> value could be handled by higher-level code, but up to now there was
    no need to do this.

    BTW, my amd64 code is assembler, so off-topic here, but arm32 code
    is mostly C. I use two helper structures:

    struct registers_buffer {
    int i_reg[4];
    union {double d; struct {float sl; float sh;} sf2;} f_reg[8];
    };

    typedef struct registers_buffer reg_buff;

    typedef struct arg_state { int ni; int sfi; int dfi; int si;} arg_state; >>>>
    C code fills 'reg_buff' with values and later low-level assembly
    copies values from the buffer to registers. I allocate enough space on >>>> the stack so that C code can write to the stack without risk of
    stack overflow.

    There are 3 helper routines:


    This looks pretty complicated, but what is it for: is it still to do
    with variadic functions, or is to with the LIBFFI problem?

    This is to handle a call, it does not matter variadic or not.
    The call is from dynamically typed language and argument types are
    known only at runtime

    OK, so it is probably performing the LIBFFI thing, or rather what needs
    to come before, which is to marshall the arguments into homogenous array
    of values. The actual LIBFFI task needs some ASM support as you say.

    As you can see from declaration of 'struct registers_buffer' values
    are no homogeneous: there is separate part for integer registers
    and separate part for floating point ones. In arm32 pair of
    single float registers overlaps with double float register,
    that is why for floating point I use a union.

    The C code is called from assembly code. C code stores stack
    arguments directly to the stack (before calling C code assembler
    ensures that there is enough space on the stack for all arguments).
    Since C code could use registers for its own purpose register
    arguments are stored in a buffer and loaded after argument loop
    is done.

    In that case, my own code to do the same (not in C) is probably more fiddly:

    https://github.com/sal55/langs/blob/master/calldll.m

    The interpreter calls 'calldll()'. 'vartopacked()' does the translation
    from tagged dynamic values to suitable FFI types. Here the args are assembled into an i64 array.

    It looks a bit simpler: AFAICS your code does not spread arguments
    between various destinations (various kinds of registers and the stack).

    The actual call is done with 'os_calldllfunction' which uses inline assembly; that has been appended.

    I also have a version of that in pure HLL code, but it only handles the
    most common combinations. (I used that if transpiling to C.)


    (actually, argument types _may_ be statically
    known at higher level, but for simplicity "all" (see below) calls go
    trough a single low-level routine that handles general dynamic case.
    Dispatcher routine (which I did not show) loops over arguments,
    decodes their types and converts them to C representation. Then
    it calls one of the 3 helper routines to place each argument in the buffer >> or on the stack.

    There is simpler integer only code which is mainly used to perform
    low level system calls. This iterface do not convert arguments
    (the assumption is that caller passes C-compatible representation)
    and logic is simpler as it just puts what fits in registers and
    the rest on the stack.

    Both interfaces spill all registers used by calling language to
    the stack before actual processing of the call. This is because
    C code may perform a callback and callback may trigger garbage

    So here the dynamic code is not interpreted?

    There are no interpreter: all code is compiled to machine code. That
    include user "command line".

    But in principle adding a bytecode intepreter is not hard. All
    functions would need a small machine code stub to start interpreter.
    There would be efficiency hit for calls to interpreted functions,
    but probably not too bad (interpreted code is slow anyway).
    ATM it is not clear to me if advantages (mainly smaller code)
    justify needed effort.

    That's a problem for me: I can't pass a the address of a callback
    function which only exists as bytecode!

    (For the purpose of running WinAPI GUI programs, I set up a special
    callback function within the compiler, and that then has to start a new interpreter instance briefly.)

    Yes, with simple enough interpreter one can start separate interpreter "instance" for each call.
    --
    Waldek Hebisch
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Tue Feb 17 23:11:36 2026
    From Newsgroup: comp.lang.c

    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:

    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:

    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:

    [...]

    I recently played around with an attempted framework using _Generic.
    The goal was to be able to write something like

    print(s(x), s(y), s(z));

    where x, y, and z can be of more or less arbitrary types (integer,
    floating-point char*). The problem I ran into was that only one of
    the generic associations is evaluated (which one is determined at
    compile time), but *all* of them have to be valid code.

    That is annoying but it shouldn't be too hard to work around
    it. To verify that hypothesis I wrote this test case:


    #include <stdio.h>
    #include <time.h>
    #include <stdint.h>

    #include "h/show.h"

    int
    main(){

    [30 lines deleted]

    show(
    uc,sc,us,ss,ui,si,ul,sl,ull,sll,
    c,f,d,ld,yes,no,u16,s16,uge32,sge32,
    runtime,now,offset,uf32,sf32,
    c * now / 1e8 * ld,
    foo, bas
    );
    printf( "\n" );

    return 0;
    }

    which compiles under C11 and (along with the show.h include file)
    produces output:

    uc = 255
    sc = -1
    us = 65535

    [23 lines deleted]

    foo = "foo"
    bas = (const char *) "bas"

    Were you planning to show us what show.h looks like?

    I posted what I thought showed the essential elements of what I
    was trying to convey. I don't want to get dragged into a
    discussion of irrelevant tangents.

    What would be more interesting is if you would post some of your
    efforts showing what sort of problems you ran into. What made
    those happen? What are the contours of the problem you were
    trying to solve? Were those contours too large, or might the
    full problem be solvable using a modified approach? It would be
    good to see a discussion of exactly what problems there were and
    what sort of approaches might address them.
    --- Synchronet 3.21b-Linux NewsLink 1.2