• Re: printf and time_t

    From David Brown@[email protected] 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 <[email protected]> wrote:

    Michael S <[email protected]> writes:
    On Sat, 10 Jan 2026 22:02:03 -0500
    "James Russell Kuyper Jr." <[email protected]> wrote:
    On 2026-01-09 07:18, Michael S wrote:
    On Thu, 8 Jan 2026 19:31:13 -0500
    "James Russell Kuyper Jr." <[email protected]>
    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@[email protected] 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." <[email protected]> wrote:

    On 2026-01-11 08:32, Michael S wrote:
    On Sun, 11 Jan 2026 04:59:47 -0800
    Keith Thompson <[email protected]> wrote:

    Michael S <[email protected]> writes:
    On Sat, 10 Jan 2026 22:02:03 -0500
    "James Russell Kuyper Jr." <[email protected]>
    wrote:
    On 2026-01-09 07:18, Michael S wrote:
    On Thu, 8 Jan 2026 19:31:13 -0500
    "James Russell Kuyper Jr." <[email protected]>
    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@[email protected] 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." <[email protected]> wrote:

    On 2026-01-11 06:20, Michael S wrote:
    On Sat, 10 Jan 2026 22:02:03 -0500
    "James Russell Kuyper Jr." <[email protected]> wrote:

    On 2026-01-09 07:18, Michael S wrote:
    On Thu, 8 Jan 2026 19:31:13 -0500
    "James Russell Kuyper Jr." <[email protected]>
    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@[email protected] to comp.lang.c on Wed Jan 14 14:53:03 2026
    From Newsgroup: comp.lang.c

    David Brown <[email protected]> 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) [email protected]
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From James Kuyper@[email protected] 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." <[email protected]> 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@[email protected] 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 <[email protected]> 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@[email protected] 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 <[email protected]> wrote:
    On 2026-01-14 04:03, Michael S wrote:
    On Tue, 13 Jan 2026 21:24:16 -0500
    "James Russell Kuyper Jr." <[email protected]> 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@[email protected] 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." <[email protected]> wrote:

    On 2026-01-11 06:20, Michael S wrote:
    On Sat, 10 Jan 2026 22:02:03 -0500
    "James Russell Kuyper Jr." <[email protected]> wrote:

    On 2026-01-09 07:18, Michael S wrote:
    On Thu, 8 Jan 2026 19:31:13 -0500
    "James Russell Kuyper Jr." <[email protected]>
    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@[email protected] to comp.lang.c on Thu Jan 15 04:00:57 2026
    From Newsgroup: comp.lang.c

    James Kuyper <[email protected]> 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) [email protected]
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From James Kuyper@[email protected] 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 <[email protected]> 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@[email protected] to comp.lang.c on Thu Jan 15 18:17:54 2026
    From Newsgroup: comp.lang.c

    James Kuyper <[email protected]> writes:
    On 2026-01-15 07:00, Keith Thompson wrote:
    James Kuyper <[email protected]> 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) [email protected]
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Tim Rentsch@[email protected] to comp.lang.c on Tue Feb 3 05:38:33 2026
    From Newsgroup: comp.lang.c

    [email protected] (Scott Lurndal) writes:

    Tim Rentsch <[email protected]> writes:

    [email protected] (Scott Lurndal) writes:

    Tim Rentsch <[email protected]> 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@[email protected] to comp.lang.c on Tue Feb 3 07:47:51 2026
    From Newsgroup: comp.lang.c

    Michael S <[email protected]> writes:

    On Sun, 11 Jan 2026 11:51:43 -0800
    Tim Rentsch <[email protected]> wrote:

    Michael S <[email protected]> writes:

    On Sat, 10 Jan 2026 22:02:03 -0500
    "James Russell Kuyper Jr." <[email protected]> wrote:

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

    On Thu, 8 Jan 2026 19:31:13 -0500
    "James Russell Kuyper Jr." <[email protected]>
    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@[email protected] 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 <[email protected]> writes:

    On Sun, 11 Jan 2026 11:51:43 -0800
    Tim Rentsch <[email protected]> wrote:

    Michael S <[email protected]> writes:

    On Sat, 10 Jan 2026 22:02:03 -0500
    "James Russell Kuyper Jr." <[email protected]> wrote:

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

    On Thu, 8 Jan 2026 19:31:13 -0500
    "James Russell Kuyper Jr." <[email protected]>
    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@[email protected] to comp.lang.c on Tue Feb 3 14:43:46 2026
    From Newsgroup: comp.lang.c

    Tim Rentsch <[email protected]> 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) [email protected]
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Bart@[email protected] 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 <[email protected]> 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@[email protected] to comp.lang.c on Tue Feb 3 15:33:44 2026
    From Newsgroup: comp.lang.c

    Bart <[email protected]> writes:
    On 03/02/2026 22:43, Keith Thompson wrote:
    Tim Rentsch <[email protected]> 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) [email protected]
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Tim Rentsch@[email protected] to comp.lang.c on Tue Feb 3 17:19:10 2026
    From Newsgroup: comp.lang.c

    Keith Thompson <[email protected]> writes:

    David Brown <[email protected]> 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@[email protected] to comp.lang.c on Tue Feb 3 18:19:45 2026
    From Newsgroup: comp.lang.c

    Tim Rentsch <[email protected]> writes:
    Keith Thompson <[email protected]> writes:
    David Brown <[email protected]> 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) [email protected]
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Tim Rentsch@[email protected] to comp.lang.c on Tue Feb 3 22:03:05 2026
    From Newsgroup: comp.lang.c

    Keith Thompson <[email protected]> writes:

    Michael S <[email protected]> writes:

    On Tue, 06 Jan 2026 16:29:04 -0800
    Keith Thompson <[email protected]> wrote:

    Michael S <[email protected]> writes:

    On Tue, 6 Jan 2026 10:31:41 -0500
    James Kuyper <[email protected]> wrote:

    On 2026-01-06 04:29, Michael S wrote:

    On Tue, 6 Jan 2026 00:27:04 -0000 (UTC)
    Lawrence D?Oliveiro <[email protected]d> 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@[email protected] to comp.lang.c on Wed Feb 4 06:22:33 2026
    From Newsgroup: comp.lang.c

    Bart <[email protected]> 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@[email protected] 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 <[email protected]> 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@[email protected] 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 <[email protected]> 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@[email protected] 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@[email protected] 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 <[email protected]> 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@[email protected] 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 <[email protected]> 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@[email protected] 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@[email protected] 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 <[email protected]> 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@[email protected] 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 <[email protected]> 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@[email protected] 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 <[email protected]> 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@[email protected] to comp.lang.c on Wed Feb 4 15:39:46 2026
    From Newsgroup: comp.lang.c

    Bart <[email protected]> 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) [email protected]
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Bart@[email protected] 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 <[email protected]> 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@[email protected] to comp.lang.c on Wed Feb 4 16:04:06 2026
    From Newsgroup: comp.lang.c

    Bart <[email protected]> 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) [email protected]
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From David Brown@[email protected] 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@[email protected] 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 <[email protected]> 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@[email protected] 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@[email protected] (Scott Lurndal) to comp.lang.c on Thu Feb 5 18:38:58 2026
    From Newsgroup: comp.lang.c

    Bart <[email protected]> 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@[email protected] to comp.lang.c on Thu Feb 5 11:05:36 2026
    From Newsgroup: comp.lang.c

    David Brown <[email protected]> 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) [email protected]
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Keith Thompson@[email protected] to comp.lang.c on Thu Feb 5 11:22:24 2026
    From Newsgroup: comp.lang.c

    David Brown <[email protected]> writes:
    On 05/02/2026 00:52, Bart wrote:
    On 04/02/2026 23:39, Keith Thompson wrote:
    Bart <[email protected]> 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) [email protected]
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Janis Papanagnou@[email protected] 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@[email protected] 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@[email protected] to comp.lang.c on Thu Feb 5 21:10:57 2026
    From Newsgroup: comp.lang.c

    Bart <[email protected]> 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) [email protected]
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Janis Papanagnou@[email protected] 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 <[email protected]> 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@[email protected] to comp.lang.c on Fri Feb 6 02:25:21 2026
    From Newsgroup: comp.lang.c

    Bart <[email protected]> writes:

    On 04/02/2026 14:22, Tim Rentsch wrote:

    Bart <[email protected]> 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@[email protected] to comp.lang.c on Fri Feb 6 02:59:01 2026
    From Newsgroup: comp.lang.c

    Keith Thompson <[email protected]> writes:

    David Brown <[email protected]> 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@[email protected] 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@[email protected] 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 <[email protected]> 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@[email protected] 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 <[email protected]> writes:
    On 05/02/2026 00:52, Bart wrote:
    On 04/02/2026 23:39, Keith Thompson wrote:
    Bart <[email protected]> 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@[email protected] 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 <[email protected]> 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@[email protected] to comp.lang.c on Fri Feb 6 04:46:51 2026
    From Newsgroup: comp.lang.c

    Janis Papanagnou <[email protected]> writes:
    On 2026-02-06 06:10, Keith Thompson wrote:
    Bart <[email protected]> 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) [email protected]
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Michael S@[email protected] 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 <[email protected]> wrote:

    On 06/02/2026 05:10, Keith Thompson wrote:
    Bart <[email protected]> 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@[email protected] to comp.lang.c on Fri Feb 6 04:49:54 2026
    From Newsgroup: comp.lang.c

    Tim Rentsch <[email protected]> writes:
    Keith Thompson <[email protected]> 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) [email protected]
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Bart@[email protected] 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 <[email protected]> wrote:

    On 06/02/2026 05:10, Keith Thompson wrote:
    Bart <[email protected]> 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@[email protected] 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 <[email protected]> writes:
    Keith Thompson <[email protected]> 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@[email protected] 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 <[email protected]> wrote:

    On 06/02/2026 05:10, Keith Thompson wrote:
    Bart <[email protected]> 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@[email protected] to comp.lang.c on Fri Feb 6 05:08:35 2026
    From Newsgroup: comp.lang.c

    Bart <[email protected]> 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) [email protected]
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Bart@[email protected] 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 <[email protected]> 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@[email protected] 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 <[email protected]> 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@[email protected] (Scott Lurndal) to comp.lang.c on Fri Feb 6 14:35:46 2026
    From Newsgroup: comp.lang.c

    Keith Thompson <[email protected]> writes:
    Bart <[email protected]> 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@[email protected] to comp.lang.c on Fri Feb 6 11:21:44 2026
    From Newsgroup: comp.lang.c

    Bart <[email protected]> writes:
    On 06/02/2026 13:08, Keith Thompson wrote:
    Bart <[email protected]> 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) [email protected]
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Bart@[email protected] 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 <[email protected]> writes:
    On 06/02/2026 13:08, Keith Thompson wrote:
    Bart <[email protected]> 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@[email protected] to comp.lang.c on Fri Feb 6 12:56:01 2026
    From Newsgroup: comp.lang.c

    Bart <[email protected]> writes:
    On 06/02/2026 19:21, Keith Thompson wrote:
    Bart <[email protected]> writes:
    On 06/02/2026 13:08, Keith Thompson wrote:
    Bart <[email protected]> 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) [email protected]
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Kaz Kylheku@[email protected] to comp.lang.c on Sat Feb 7 17:55:23 2026
    From Newsgroup: comp.lang.c

    On 2026-02-05, Bart <[email protected]> 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: @[email protected]
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Kaz Kylheku@[email protected] to comp.lang.c on Sat Feb 7 18:07:48 2026
    From Newsgroup: comp.lang.c

    On 2026-02-06, Michael S <[email protected]> wrote:
    On Fri, 6 Feb 2026 12:39:55 +0000
    Bart <[email protected]> wrote:

    On 06/02/2026 05:10, Keith Thompson wrote:
    Bart <[email protected]> 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: @[email protected]
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Bart@[email protected] 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 <[email protected]> wrote:
    On Fri, 6 Feb 2026 12:39:55 +0000
    Bart <[email protected]> wrote:

    On 06/02/2026 05:10, Keith Thompson wrote:
    Bart <[email protected]> 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@[email protected] 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 <[email protected]> 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@[email protected] (Waldek Hebisch) to comp.lang.c on Sat Feb 7 22:48:32 2026
    From Newsgroup: comp.lang.c

    Bart <[email protected]> wrote:
    On 07/02/2026 18:07, Kaz Kylheku wrote:
    On 2026-02-06, Michael S <[email protected]> wrote:
    On Fri, 6 Feb 2026 12:39:55 +0000
    Bart <[email protected]> wrote:

    On 06/02/2026 05:10, Keith Thompson wrote:
    Bart <[email protected]> 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@[email protected] 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 <[email protected]> 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@[email protected] 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 <[email protected]> wrote:

    On 06/02/2026 12:47, Michael S wrote:
    On Fri, 6 Feb 2026 12:39:55 +0000
    Bart <[email protected]> wrote:

    On 06/02/2026 05:10, Keith Thompson wrote:
    Bart <[email protected]> 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@[email protected] 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 <[email protected]> wrote:
    On 06/02/2026 12:47, Michael S wrote:
    On Fri, 6 Feb 2026 12:39:55 +0000
    Bart <[email protected]> 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@[email protected] to comp.lang.c on Sun Feb 8 04:03:23 2026
    From Newsgroup: comp.lang.c

    Janis Papanagnou <[email protected]> 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) [email protected]
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From antispam@[email protected] (Waldek Hebisch) to comp.lang.c on Sun Feb 8 16:50:22 2026
    From Newsgroup: comp.lang.c

    Michael S <[email protected]> wrote:
    On Fri, 6 Feb 2026 13:04:34 +0000
    Bart <[email protected]> wrote:

    On 06/02/2026 12:47, Michael S wrote:
    On Fri, 6 Feb 2026 12:39:55 +0000
    Bart <[email protected]> wrote:

    On 06/02/2026 05:10, Keith Thompson wrote:
    Bart <[email protected]> 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@[email protected] 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 <[email protected]> wrote:
    On Fri, 6 Feb 2026 13:04:34 +0000
    Bart <[email protected]> wrote:

    On 06/02/2026 12:47, Michael S wrote:
    On Fri, 6 Feb 2026 12:39:55 +0000
    Bart <[email protected]> wrote:

    On 06/02/2026 05:10, Keith Thompson wrote:
    Bart <[email protected]> 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@[email protected] (Waldek Hebisch) to comp.lang.c on Sun Feb 8 19:21:41 2026
    From Newsgroup: comp.lang.c

    Bart <[email protected]> wrote:
    On 07/02/2026 22:48, Waldek Hebisch wrote:
    Bart <[email protected]> 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@[email protected] 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 <[email protected]> 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@[email protected] 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 <[email protected]> wrote:
    On Fri, 6 Feb 2026 13:04:34 +0000
    Bart <[email protected]> wrote:

    On 06/02/2026 12:47, Michael S wrote:
    On Fri, 6 Feb 2026 12:39:55 +0000
    Bart <[email protected]> wrote:
    On 06/02/2026 05:10, Keith Thompson wrote:
    Bart <[email protected]> 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@[email protected] (Waldek Hebisch) to comp.lang.c on Sun Feb 8 19:59:01 2026
    From Newsgroup: comp.lang.c

    David Brown <[email protected]> wrote:
    On 08/02/2026 17:50, Waldek Hebisch wrote:
    Michael S <[email protected]> wrote:
    On Fri, 6 Feb 2026 13:04:34 +0000
    Bart <[email protected]> wrote:

    On 06/02/2026 12:47, Michael S wrote:
    On Fri, 6 Feb 2026 12:39:55 +0000
    Bart <[email protected]> wrote:

    On 06/02/2026 05:10, Keith Thompson wrote:
    Bart <[email protected]> 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@[email protected] 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 <[email protected]> wrote:
    On Fri, 6 Feb 2026 13:04:34 +0000
    Bart <[email protected]> wrote:

    On 06/02/2026 12:47, Michael S wrote:
    On Fri, 6 Feb 2026 12:39:55 +0000
    Bart <[email protected]> wrote:
    On 06/02/2026 05:10, Keith Thompson wrote:
    Bart <[email protected]> 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@[email protected] 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@[email protected] 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 <[email protected]> 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@[email protected] (Waldek Hebisch) to comp.lang.c on Mon Feb 9 01:27:43 2026
    From Newsgroup: comp.lang.c

    Bart <[email protected]> wrote:
    On 08/02/2026 19:21, Waldek Hebisch wrote:
    Bart <[email protected]> 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@[email protected] (Waldek Hebisch) to comp.lang.c on Mon Feb 9 01:40:02 2026
    From Newsgroup: comp.lang.c

    Bart <[email protected]> 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@[email protected] 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 <[email protected]> wrote:
    On 08/02/2026 19:21, Waldek Hebisch wrote:
    Bart <[email protected]> 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@[email protected] (Waldek Hebisch) to comp.lang.c on Tue Feb 10 13:22:03 2026
    From Newsgroup: comp.lang.c

    Bart <[email protected]> wrote:
    On 09/02/2026 01:27, Waldek Hebisch wrote:
    Bart <[email protected]> wrote:
    On 08/02/2026 19:21, Waldek Hebisch wrote:
    Bart <[email protected]> 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@[email protected] to comp.lang.c on Tue Feb 17 23:11:36 2026
    From Newsgroup: comp.lang.c

    Keith Thompson <[email protected]> writes:

    Tim Rentsch <[email protected]> writes:

    Keith Thompson <[email protected]> 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
  • From Tim Rentsch@[email protected] to comp.lang.c on Sun Mar 1 21:05:19 2026
    From Newsgroup: comp.lang.c

    Keith Thompson <[email protected]> writes:

    Tim Rentsch <[email protected]> writes:

    Keith Thompson <[email protected]> writes:

    David Brown <[email protected]> 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, [...]

    In my opinion it does.
    --- Synchronet 3.21c-Linux NewsLink 1.2
  • From Tim Rentsch@[email protected] to comp.lang.c on Sun Mar 1 23:21:05 2026
    From Newsgroup: comp.lang.c

    "James Russell Kuyper Jr." <[email protected]> writes:

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

    James Kuyper <[email protected]> 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. [...]

    The C standard doesn't say that time_t is an opaque type. What it
    does say (in the C99 standard) is that it is an arithmetic type
    capable of representing times, and that the range and precision of
    those times are implementation defined. The idea that time_t is an
    opaque type doesn't mesh with that description, nor does it mesh
    with historical usage that predates even the earliest work on the
    original (ANSI) C standard. Even if we don't know the range and
    precision, we expect to be able to operate on time values with
    standard arithmetic operators, but that doesn't work for complex
    or imaginary values because of the rules for relational operators.

    Besides all of that, _Imaginary types don't satisify the condition
    for time_t to be an arithmetic type. Annex G says the imaginary
    types are floating types, but in C99 Annex G is informative, not
    normative.
    --- Synchronet 3.21c-Linux NewsLink 1.2
  • From Keith Thompson@[email protected] to comp.lang.c on Mon Mar 2 03:37:22 2026
    From Newsgroup: comp.lang.c

    Tim Rentsch <[email protected]> writes:
    "James Russell Kuyper Jr." <[email protected]> writes:
    On 2026-01-07 08:02, Tim Rentsch wrote:
    James Kuyper <[email protected]> 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. [...]

    The C standard doesn't say that time_t is an opaque type.

    Nor does it say that it isn't. The C standard does not define or use
    the word "opaque". I'm not sure I would have said that time_t is an
    "opaque type", but the usage doesn't seem entirely unreasonable.

    Informally, it's usually best to *treat* time_t as opaque (much
    like type FILE can informally be considered opaque, more so than
    time_t), dealing with it only via functions declared in <time.h>.
    You can apply arithmetic operations to values of type time_t, but
    the results are not necessarily meaningful. (And of course you
    can write code whose behavior depends on how time_t is defined,
    which makes it, I suppose, not entirely opaque.)

    [...]

    Besides all of that, _Imaginary types don't satisify the condition
    for time_t to be an arithmetic type. Annex G says the imaginary
    types are floating types, but in C99 Annex G is informative, not
    normative.

    In a C99 implementation that supports imaginary types in accordance
    with Annex G, they are arithmetic types, and therefore candidates
    for the underlying type of time_t. (Making time_t imaginary would
    be silly but conforming.)

    (In C11 and later, Annex G is normative. The main difference is that
    a C11 or later implementation that defines __STDC_IEC_559_COMPLEX__
    "shall", rather than "should", conform to the specifications to
    the annex. C23 changes the macro to __STDC_IEC_60559_COMPLEX__
    and makes __STDC_IEC_559_COMPLEX__ obsolescent.)
    --
    Keith Thompson (The_Other_Keith) [email protected]
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21c-Linux NewsLink 1.2
  • From James Kuyper@[email protected] to comp.lang.c on Mon Mar 2 17:53:34 2026
    From Newsgroup: comp.lang.c

    Tim Rentsch <[email protected]> writes:
    "James Russell Kuyper Jr." <[email protected]> writes:
    On 2026-01-07 08:02, Tim Rentsch wrote:
    James Kuyper <[email protected]> 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. [...]

    The C standard doesn't say that time_t is an opaque type.

    True. That's a conclusion that can be reached by the reader from the
    lack of information provided by the C standard about the type. The
    opacity of the type is demonstrated by that lack of information.


    Besides all of that, _Imaginary types don't satisify the condition
    for time_t to be an arithmetic type. Annex G says the imaginary
    types are floating types, but in C99 Annex G is informative, not
    normative.

    True, but if _Imaginary types are supported by an implementation, Annex
    G describes what it is that is being supported. I don't see a problem,
    though I'm sure that you do.
    I hadn't remembered that the imaginary types were removed from the draft version of C202X. They were still present in n3220.pdf, dated 2024. In
    that version, Annex G (which describes imaginary types) is described as normative. 7.3.1p6 indicates that they were optional in that version.
    However, if they were supported, they did meet the requirements of an arithmetic type.
    --- Synchronet 3.21d-Linux NewsLink 1.2
  • From Keith Thompson@[email protected] to comp.lang.c on Mon Mar 2 18:04:13 2026
    From Newsgroup: comp.lang.c

    James Kuyper <[email protected]> writes:
    Tim Rentsch <[email protected]> writes:
    [...]
    Besides all of that, _Imaginary types don't satisify the condition
    for time_t to be an arithmetic type. Annex G says the imaginary
    types are floating types, but in C99 Annex G is informative, not
    normative.

    True, but if _Imaginary types are supported by an implementation, Annex
    G describes what it is that is being supported. I don't see a problem,
    though I'm sure that you do.
    I hadn't remembered that the imaginary types were removed from the draft version of C202X. They were still present in n3220.pdf, dated 2024. In
    that version, Annex G (which describes imaginary types) is described as normative. 7.3.1p6 indicates that they were optional in that version. However, if they were supported, they did meet the requirements of an arithmetic type.

    I don't have the official ISO C23 standard, but I understand
    that N3220 is quite close to it. In that draft, and I believe
    in the actual standard, Annex G is normative but optional.
    Implementations are not required to support it unless they
    define __STDC_IEC_559_COMPLEX__. (C23 changes the macro
    name to __STDC_IEC_60559_COMPLEX__ and makes the name
    __STDC_IEC_559_COMPLEX__ obsolescent.)

    C11 and C17 are similar.

    (As of C23, Annex G still says "There is a new keyword _Imaginary".
    It's hardly new; it goes back to C99.)

    As I understand it, a conforming implementation that defines __STDC_IEC_559_COMPLEX__ or __STDC_IEC_60559_COMPLEX__ *must*
    implement both complex and imaginary types. Neither gcc nor clang
    does so.

    C2y completely drops support for imaginary types.
    --
    Keith Thompson (The_Other_Keith) [email protected]
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.21d-Linux NewsLink 1.2