James Kuyper <jameskuyper@alumni.caltech.edu> writes:
On 2026-01-05 11:34, David Brown wrote:
...
As I understand it, time_t is intended to be suitable for holding a
number of seconds ...
The standard says nothing about that.
... (it is used for that purpose in struct timespec). ...
The standard says nothing to connect time_t to struct timespec.
Yes, but also no.
struct timespec contains at least the following members, in any order:
time_t tv_sec; // whole seconds — ≥ 0
long tv_nsec; // nanoseconds — [0, 999999999]
But a footnote says:
The tv_sec member is a linear count of seconds and may not have
the normal semantics of a time_t.
This makes for a simpler implementation *if* the time_t value
returned by time(), and operated on by difftime(), mktime(), ctime(), gmtime(), and localtime(), happens to hold a linear count of seconds
(as it does on most systems).
It's odd that time_t is used for two potentially very different
purposes, one as a member of struct timespec and another as used
in all other contexts.
And I would guess that a lot of code that
uses struct timespec *assumes* that its time_t member has the same
semantics value returned by time(NULL).
For example, as I write this the time is 2026-01-05 22:32:57.881 UTC.
The corresponding value returned by time() is 1767652377 (seconds
since the 1970 epoch, no milliseconds). An implementation could
represent the current time (the value returned by time(NULL) as a
64-bit integer with the value 20260105223257881. But timespec_get()
would still have to set the tv_sec member to 1767652377.
It might have been cleaner either to require that time_t represents a
count of seconds, or to use a type other than time_t for the tv_sec
member of struct timespec.
I know there are systems that use something other than seconds
since 1970 in the underlying time representation, but are there any
C implementations that don't use seconds since 1970? (POSIX and
Windows both specify that.)
On Mon, 5 Jan 2026 16:23:09 -0000 (UTC), Lew Pitcher wrote:
As Andrey pointed out, time_t can resolve to a floatingpoint type,
POSIX says time_t is an integer type <https://manpages.debian.org/time_t(3type)>.
On Tue, 6 Jan 2026 00:27:04 -0000 (UTC)...
Lawrence D’Oliveiro <ldo@nz.invalid> wrote:
Section 7.8 of the C spec defines macros you can use so you don’t have
to hard-code assumptions about the lengths of integers in
printf-format strings.
Did you ever try to use them? They look ugly.
On 2026-01-05 19:22, Lawrence D’Oliveiro wrote:
On Mon, 5 Jan 2026 16:23:09 -0000 (UTC), Lew Pitcher wrote:
As Andrey pointed out, time_t can resolve to a floatingpoint type,
POSIX says time_t is an integer type
<https://manpages.debian.org/time_t(3type)>.
Which means that implementations where time_t has a floating point type cannot comply with POSIX; many implementations fail to do so.
On Tue, 06 Jan 2026 10:22:28 -0500, James Kuyper wrote:
On 2026-01-05 19:22, Lawrence D’Oliveiro wrote:
On Mon, 5 Jan 2026 16:23:09 -0000 (UTC), Lew Pitcher wrote:
As Andrey pointed out, time_t can resolve to a floatingpoint type,
POSIX says time_t is an integer type
Actually, POSIX does not say that.
<https://manpages.debian.org/time_t(3type)>.
The Debian manpages do not necessarily represent current POSIX standards.
The current online POSIX standards pages say that, among others, time_t "shall be defined as arithmetic types of an appropriate length" (https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_types.h.html)
Lew Pitcher wrote:
On Tue, 06 Jan 2026 10:22:28 -0500, James Kuyper wrote:
On 2026-01-05 19:22, Lawrence D’Oliveiro wrote:
On Mon, 5 Jan 2026 16:23:09 -0000 (UTC), Lew Pitcher wrote:
As Andrey pointed out, time_t can resolve to a floatingpoint type,
POSIX says time_t is an integer type
Actually, POSIX does not say that.
<https://manpages.debian.org/time_t(3type)>.
The Debian manpages do not necessarily represent current POSIX standards.
The current online POSIX standards pages say that, among others, time_t
"shall be defined as arithmetic types of an appropriate length"
(https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_types.h.html)
Looks like you looked at an old version. Currently there is:
|
| [CX] time_t shall be an integer type with a width (see <stdint.h>) of at least 64 bits.
| [...]
| Austin Group Defect 1462 is applied, changing time_t to have a width of at least 64 bits.
It refers to this issue:--
<https://www.austingroupbugs.net/view.php?id=1462>
Lew Pitcher wrote:
On Tue, 06 Jan 2026 10:22:28 -0500, James Kuyper wrote:
On 2026-01-05 19:22, Lawrence D’Oliveiro wrote:
On Mon, 5 Jan 2026 16:23:09 -0000 (UTC), Lew Pitcher wrote:
As Andrey pointed out, time_t can resolve to a floatingpoint type,
POSIX says time_t is an integer type
Actually, POSIX does not say that.
<https://manpages.debian.org/time_t(3type)>.
The Debian manpages do not necessarily represent current POSIX standards.
The current online POSIX standards pages say that, among others, time_t
"shall be defined as arithmetic types of an appropriate length"
(https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_types.h.html)
Looks like you looked at an old version. Currently there is:
|
| [CX] time_t shall be an integer type with a width (see <stdint.h>) of at least 64 bits.
| [...]
| Austin Group Defect 1462 is applied, changing time_t to have a width of at least 64 bits.
It refers to this issue:
<https://www.austingroupbugs.net/view.php?id=1462>
On Tue, 06 Jan 2026 17:00:43 +0100, Michael Bäuerle wrote:...
Lew Pitcher wrote:
The current online POSIX standards pages say that, among others, time_t
"shall be defined as arithmetic types of an appropriate length"
(https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_types.h.html)
Looks like you looked at an old version. Currently there is:
|
| [CX] time_t shall be an integer type with a width (see <stdint.h>) of at least 64 bits.
| [...]
| Austin Group Defect 1462 is applied, changing time_t to have a width of at least 64 bits.
Do you have a URL reference for this? I got my POSIX references direct from the Open Group's
"current standards" links. The time(2), <time.h> and <sys/types.h> webpages are all
headed:
The Open Group Base Specifications Issue 8
IEEE Std 1003.1-2024
Perhaps they have not yet applied the defect remediation to the online reference.
On 2026-01-06 04:29, Michael S wrote:
On Tue, 6 Jan 2026 00:27:04 -0000 (UTC)...
Lawrence D’Oliveiro <ldo@nz.invalid> wrote:
Section 7.8 of the C spec defines macros you can use so you don’t
have to hard-code assumptions about the lengths of integers in
printf-format strings.
Did you ever try to use them? They look ugly.
Which is more important, correctness or beauty?
If you know that an expression has one of the standard-named types or typedefs for with there is a corresponding printf() specifier, youI should? Really?
should use that specifier. Otherwise, if you know that an expression
has one of the types declared in <stdint.h>, you should use the
corresponding macro #defined in <inttypes.h> to print it.
If you haveThat is better advice.
a value that is not known to be of one of those types, but is known
to be convertible to one of those types without change of value, you
should convert it to one of those types.
On Tue, 6 Jan 2026 10:31:41 -0500
James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
If you know that an expression has one of the standard-named types or
typedefs for with there is a corresponding printf() specifier, you
should use that specifier. Otherwise, if you know that an expression
has one of the types declared in <stdint.h>, you should use the
corresponding macro #defined in <inttypes.h> to print it.
I should? Really?
Sorry, James, but you have no authority to make such statements.
On Tue, 6 Jan 2026 10:31:41 -0500
James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
On 2026-01-06 04:29, Michael S wrote:
On Tue, 6 Jan 2026 00:27:04 -0000 (UTC)...
Lawrence D’Oliveiro <ldo@nz.invalid> wrote:
Section 7.8 of the C spec defines macros you can use so you don’t
have to hard-code assumptions about the lengths of integers in
printf-format strings.
Did you ever try to use them? They look ugly.
Which is more important, correctness or beauty?
It depends.
When I know for sure that incorrectness has no consequences, like
in case of using %u to print 'unsigned long' on target with 32-bit
longs, or like using %llu to print 'unsigned long' on target with
64-bit longs, then beauty wins. Easily.
On Tue, 6 Jan 2026 10:31:41 -0500
James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
On 2026-01-06 04:29, Michael S wrote:
On Tue, 6 Jan 2026 00:27:04 -0000 (UTC)...
Lawrence D’Oliveiro <ldo@nz.invalid> wrote:
Section 7.8 of the C spec defines macros you can use so you don’t
have to hard-code assumptions about the lengths of integers in
printf-format strings.
Did you ever try to use them? They look ugly.
Which is more important, correctness or beauty?
It depends.
When I know for sure that incorrectness has no consequences, like
in case of using %u to print 'unsigned long' on target with 32-bit
longs, or like using %llu to print 'unsigned long' on target with
64-bit longs, then beauty wins. Easily.
If you know that an expression has one of the standard-named types or
typedefs for with there is a corresponding printf() specifier, you
should use that specifier. Otherwise, if you know that an expression
has one of the types declared in <stdint.h>, you should use the
corresponding macro #defined in <inttypes.h> to print it.
I should? Really?
Sorry, James, but you have no authority to make such statements.
I agree that the macros in <stdint.h> are ugly, and I rarely
use them. If I want to print an integer value whose type I don't
know, I'll probably cast to a predefined type that I know to be
wide enough and use the specifier for that type. Though now that
I think about it, I'm more likely to do that in throwaway code;
for production code, I'd be more likely to use the <stdint.h> macros.
On 2026-01-06 13:05, Michael S wrote:
in case of using %u to print 'unsigned long' on target with 32-bit
longs, or like using %llu to print 'unsigned long' on target with
64-bit longs, then beauty wins. Easily.
You've got it backwards. "%u" is the correct specifier to use for
unsigned long on all platforms, whether unsigned long is 32, 36, or even
48 bits.
On 07/01/2026 00:44, James Kuyper wrote:
On 2026-01-06 13:05, Michael S wrote:
in case of using %u to print 'unsigned long' on target with 32-bit
longs, or like using %llu to print 'unsigned long' on target with
64-bit longs, then beauty wins. Easily.
You've got it backwards. "%u" is the correct specifier to use for
unsigned long on all platforms, whether unsigned long is 32, 36, or
even 48 bits.
So not "%lu"?
Michael S <already5chosen@yahoo.com> writes:
On Tue, 6 Jan 2026 10:31:41 -0500
James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
On 2026-01-06 04:29, Michael S wrote:
On Tue, 6 Jan 2026 00:27:04 -0000 (UTC)...
Lawrence D’Oliveiro <ldo@nz.invalid> wrote:
Section 7.8 of the C spec defines macros you can use so you
don’t have to hard-code assumptions about the lengths of
integers in printf-format strings.
Did you ever try to use them? They look ugly.
Which is more important, correctness or beauty?
It depends.
When I know for sure that incorrectness has no consequences, like
in case of using %u to print 'unsigned long' on target with 32-bit
longs, or like using %llu to print 'unsigned long' on target with
64-bit longs, then beauty wins. Easily.
Seriously?
An example:
unsigned long n = 42;
printf("%u\n", n); // incorrect
printf("%lu\n", n); // correct
Are you really saying that the second version is so much uglier
than the first that you'd rather write incorrect code?
If unsigned int and unsigned long happen to be the same size, both
are likely to print "42". But what if your code is later compiled
on a system with 32-bit unsigned int and 64-bit unsigned long?
Even if I were certain the code would never be ported (and such
certainty is often unjustified), I'd much rather use the correct
code than waste time figuring out which incorrect code will happen
to "work" on the current system, with the current version of the
compiler and runtime library. Oh, and gcc and clang both warn
about an incorrect format string.
I agree that the macros in <stdint.h> are ugly, and I rarely
use them. If I want to print an integer value whose type I don't
know, I'll probably cast to a predefined type that I know to be
wide enough and use the specifier for that type. Though now that
I think about it, I'm more likely to do that in throwaway code;
for production code, I'd be more likely to use the <stdint.h> macros.
[...]
On Tue, 06 Jan 2026 16:29:04 -0800
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
Michael S <already5chosen@yahoo.com> writes:
On Tue, 6 Jan 2026 10:31:41 -0500
James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
On 2026-01-06 04:29, Michael S wrote:
On Tue, 6 Jan 2026 00:27:04 -0000 (UTC)...
Lawrence D’Oliveiro <ldo@nz.invalid> wrote:
Section 7.8 of the C spec defines macros you can use so you
don’t have to hard-code assumptions about the lengths of
integers in printf-format strings.
Did you ever try to use them? They look ugly.
Which is more important, correctness or beauty?
It depends.
When I know for sure that incorrectness has no consequences, like
in case of using %u to print 'unsigned long' on target with 32-bit
longs, or like using %llu to print 'unsigned long' on target with
64-bit longs, then beauty wins. Easily.
Seriously?
An example:
unsigned long n = 42;
printf("%u\n", n); // incorrect
printf("%lu\n", n); // correct
Are you really saying that the second version is so much uglier
than the first that you'd rather write incorrect code?
No, I don't think that it is much uglier. At worst, I think that
correct version is tiny bit uglier. Not enough for beauty to win over "correctness", even when correctness is non-consequential.
I hoped that you followed the sub-thread from the beginning and did not
lost the context yet.
Which is (everywhere except LIN64)
uint32_t n = 42;
printf("n = %u\n", n); // incorrect
printf("n = " PRIu32 "\n", n); // correct
or on LIN64
uint64_t n = 42;
printf("n = %llu\n", n); // incorrect
printf("n = " PRIu64 "\n", n); // correct
Here in my book beauty wins by landslide.
Although really it is not beauty wins. It's ugliness loses.
I am happy that in practice your position is not too different from my position. It's just that irresistible urge of you to defend "right"
things in NG discussions that creates an appearance of disagreeing.
On 2026-01-05 03:17, Andrey Tarasevich wrote:
On Sun 1/4/2026 11:19 PM, Kenny McCormack wrote:
The question is: How can you reliably printf() a time_t value?
What conversion spec should you use?
You can't. As far as the language is concerned, `time_t` is intended
to be an opaque type. It has to be a real type, ...
In C99, it was only required to be an arithmetic type. I pointed out
that this would permit it to be, for example, double _Imaginary. [...]
Michael S <already5chosen@yahoo.com> writes:
On Tue, 6 Jan 2026 10:31:41 -0500
James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
If you know that an expression has one of the standard-named types or
typedefs for with there is a corresponding printf() specifier, you
should use that specifier. Otherwise, if you know that an expression
has one of the types declared in <stdint.h>, you should use the
corresponding macro #defined in <inttypes.h> to print it.
I should? Really?
Sorry, James, but you have no authority to make such statements.
James is paraphrasing the C standard.
Michael S <already5chosen@yahoo.com> writes:
On Tue, 6 Jan 2026 10:31:41 -0500
James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
On 2026-01-06 04:29, Michael S wrote:
On Tue, 6 Jan 2026 00:27:04 -0000 (UTC)...
Lawrence D’Oliveiro <ldo@nz.invalid> wrote:
Section 7.8 of the C spec defines macros you can use so you don’t
have to hard-code assumptions about the lengths of integers in
printf-format strings.
Did you ever try to use them? They look ugly.
Which is more important, correctness or beauty?
It depends.
When I know for sure that incorrectness has no consequences, like
in case of using %u to print 'unsigned long' on target with 32-bit
longs, or like using %llu to print 'unsigned long' on target with
64-bit longs, then beauty wins. Easily.
Seriously?
An example:
unsigned long n = 42;
printf("%u\n", n); // incorrect
printf("%lu\n", n); // correct
Are you really saying that the second version is so much uglier
than the first that you'd rather write incorrect code?
On Wed, 7 Jan 2026 01:14:21 +0000
bart <bc@freeuk.com> wrote:
On 07/01/2026 00:44, James Kuyper wrote:
On 2026-01-06 13:05, Michael S wrote:
in case of using %u to print 'unsigned long' on target with 32-bit
longs, or like using %llu to print 'unsigned long' on target with
64-bit longs, then beauty wins. Easily.
You've got it backwards. "%u" is the correct specifier to use for
unsigned long on all platforms, whether unsigned long is 32, 36, or
even 48 bits.
So not "%lu"?
gcc and clang maintainers certainly think so.
On 07/01/2026 11:41, Michael S wrote:
On Wed, 7 Jan 2026 01:14:21 +0000
bart <bc@freeuk.com> wrote:
On 07/01/2026 00:44, James Kuyper wrote:
On 2026-01-06 13:05, Michael S wrote:
in case of using %u to print 'unsigned long' on target with
32-bit longs, or like using %llu to print 'unsigned long' on
target with 64-bit longs, then beauty wins. Easily.
You've got it backwards. "%u" is the correct specifier to use for
unsigned long on all platforms, whether unsigned long is 32, 36,
or even 48 bits.
So not "%lu"?
gcc and clang maintainers certainly think so.
They think it is correct or not correct? If I compile this:
#include <stdio.h>
int main() {
unsigned long a=0;
printf("%u", a);
}
then gcc complains (given suitable options):
warning: format '%u' expects argument of type 'unsigned int', but
argument 2 has type 'long unsigned int' [-Wformat=]
The suggests it is not correct.
On 07/01/2026 00:44, James Kuyper wrote:
On 2026-01-06 13:05, Michael S wrote:
in case of using %u to print 'unsigned long' on target with 32-bit
longs, or like using %llu to print 'unsigned long' on target with
64-bit longs, then beauty wins. Easily.
You've got it backwards. "%u" is the correct specifier to use for
unsigned long on all platforms, whether unsigned long is 32, 36, or even
48 bits.
So not "%lu"?
James Kuyper <jameskuyper@alumni.caltech.edu> writes:...
On 2026-01-05 03:17, Andrey Tarasevich wrote:
You can't. As far as the language is concerned, `time_t` is intended
to be an opaque type. It has to be a real type, ...
In C99, it was only required to be an arithmetic type. I pointed out
that this would permit it to be, for example, double _Imaginary. [...]
It's hard to imagine how time_t being an imaginary type could
provide the semantics described in the C standard for time_t.
scott@slp53.sl.home (Scott Lurndal) writes:
Michael S <already5chosen@yahoo.com> writes:
On Tue, 6 Jan 2026 10:31:41 -0500
James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
If you know that an expression has one of the standard-named types or
typedefs for with there is a corresponding printf() specifier, you
should use that specifier. Otherwise, if you know that an expression
has one of the types declared in <stdint.h>, you should use the
corresponding macro #defined in <inttypes.h> to print it.
I should? Really?
Sorry, James, but you have no authority to make such statements.
James is paraphrasing the C standard.
Really? What passage in the C standard is being paraphrased?
On a different point, I used time_t as an example. It would have
been better to use ptrdiff_t instead, since <inttypes.h> has a macro
for that type, and doesn't have one for time_t.
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
Michael S <already5chosen@yahoo.com> writes:
On Tue, 6 Jan 2026 10:31:41 -0500
James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
On 2026-01-06 04:29, Michael S wrote:
On Tue, 6 Jan 2026 00:27:04 -0000 (UTC)...
Lawrence D’Oliveiro <ldo@nz.invalid> wrote:
Section 7.8 of the C spec defines macros you can use so you don’t
have to hard-code assumptions about the lengths of integers in
printf-format strings.
Did you ever try to use them? They look ugly.
Which is more important, correctness or beauty?
It depends.
When I know for sure that incorrectness has no consequences, like
in case of using %u to print 'unsigned long' on target with 32-bit
longs, or like using %llu to print 'unsigned long' on target with
64-bit longs, then beauty wins. Easily.
Seriously?
An example:
unsigned long n = 42;
printf("%u\n", n); // incorrect
printf("%lu\n", n); // correct
Are you really saying that the second version is so much uglier
than the first that you'd rather write incorrect code?
I suspect he may have been referring to code that needs
to build for both 32-bit and 64-bit targets. One might
typedef 'uint64' to be unsigned long long on both targets
and just use %llu for the format string. BTDT.
On Wed, 7 Jan 2026 12:45:30 -0500, James Russell Kuyper Jr. wrote:
On a different point, I used time_t as an example. It would have
been better to use ptrdiff_t instead, since <inttypes.h> has a macro
for that type, and doesn't have one for time_t.
This is why you have configure scripts, so they can figure out the
right types to use for building on your platform.
scott@slp53.sl.home (Scott Lurndal) writes:
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
Michael S <already5chosen@yahoo.com> writes:
On Tue, 6 Jan 2026 10:31:41 -0500
James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
On 2026-01-06 04:29, Michael S wrote:
On Tue, 6 Jan 2026 00:27:04 -0000 (UTC)...
Lawrence D’Oliveiro <ldo@nz.invalid> wrote:
Section 7.8 of the C spec defines macros you can use so you
don’t have to hard-code assumptions about the lengths of
integers in printf-format strings.
Did you ever try to use them? They look ugly.
Which is more important, correctness or beauty?
It depends.
When I know for sure that incorrectness has no consequences, like
in case of using %u to print 'unsigned long' on target with 32-bit
longs, or like using %llu to print 'unsigned long' on target with
64-bit longs, then beauty wins. Easily.
Seriously?
An example:
unsigned long n = 42;
printf("%u\n", n); // incorrect
printf("%lu\n", n); // correct
Are you really saying that the second version is so much uglier
than the first that you'd rather write incorrect code?
I suspect he may have been referring to code that needs
to build for both 32-bit and 64-bit targets. One might
typedef 'uint64' to be unsigned long long on both targets
and just use %llu for the format string. BTDT.
In the quoted paragraph above, Michael wrote about using %u to print
unsigned long, not about using %u to print some type hidden behind
a typedef. If he didn't mean that, he can say so.
But even if he meant to talk about printing, say, uint64_t values,
my point stands.
I wouldn't define my own "uint64" type. I'd just use "uint64_t",
defined in <stdint.h>. And I'd use one of several *correct* ways
to print uint64_t values.
Michael, if you'd care to clarify, given:
unsigned long n = 42;
printf("%u\n", n); // incorrect
printf("%lu\n", n); // correct
(and assuming that unsigned int and unsigned long are the same width
on the current implementation), do you really prefer the version
marked as "incorrect"?
Lawrence D’Oliveiro <ldo@nz.invalid> writes:
On Wed, 7 Jan 2026 12:45:30 -0500, James Russell Kuyper Jr. wrote:
On a different point, I used time_t as an example. It would have
been better to use ptrdiff_t instead, since <inttypes.h> has a
macro for that type, and doesn't have one for time_t.
This is why you have configure scripts, so they can figure out the
right types to use for building on your platform.
I don't follow. ptrdiff_t is defined in <stddef.h>, and is the
correct type for the result of subtracting two pointers. What
relevant information would a configure script give you?
On Wed, 07 Jan 2026 13:28:45 -0800
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
scott@slp53.sl.home (Scott Lurndal) writes:
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
Michael S <already5chosen@yahoo.com> writes:
On Tue, 6 Jan 2026 10:31:41 -0500
James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
On 2026-01-06 04:29, Michael S wrote:
On Tue, 6 Jan 2026 00:27:04 -0000 (UTC)...
Lawrence D’Oliveiro <ldo@nz.invalid> wrote:
Section 7.8 of the C spec defines macros you can use so you
don’t have to hard-code assumptions about the lengths of
integers in printf-format strings.
Did you ever try to use them? They look ugly.
Which is more important, correctness or beauty?
It depends.
When I know for sure that incorrectness has no consequences, like
in case of using %u to print 'unsigned long' on target with 32-bit
longs, or like using %llu to print 'unsigned long' on target with
64-bit longs, then beauty wins. Easily.
Seriously?
An example:
unsigned long n = 42;
printf("%u\n", n); // incorrect
printf("%lu\n", n); // correct
Are you really saying that the second version is so much uglier
than the first that you'd rather write incorrect code?
I suspect he may have been referring to code that needs
to build for both 32-bit and 64-bit targets. One might
typedef 'uint64' to be unsigned long long on both targets
and just use %llu for the format string. BTDT.
In the quoted paragraph above, Michael wrote about using %u to print
unsigned long, not about using %u to print some type hidden behind
a typedef. If he didn't mean that, he can say so.
But even if he meant to talk about printing, say, uint64_t values,
my point stands.
I wouldn't define my own "uint64" type. I'd just use "uint64_t",
defined in <stdint.h>. And I'd use one of several *correct* ways
to print uint64_t values.
Michael, if you'd care to clarify, given:
unsigned long n = 42;
printf("%u\n", n); // incorrect
printf("%lu\n", n); // correct
(and assuming that unsigned int and unsigned long are the same width
on the current implementation), do you really prefer the version
marked as "incorrect"?
I hoped that I already clarified that point more than one time.
Obviously, I hoped wrong.
In the case I am talking about n declared as uint32_t.
uint32_t is an alias of 'unsigned long' on 32-bit embedded targets, on
32-bit Linux, on 32-bit Windows and on 64-bit Windows. It is
alias of 'unsigned int' on 64-bit Linux.
Sometimes I move code between targets by myself, sometimes, rarely,
other people do it. I don't want to have different versions of the code
and I don't want to use ugly standard specifiers. Between two pretty
and working variants I prefer the shorter one. Partly because it is guaranteed to work correctly on all my targets, including LIN64, but
more importantly (in practice, 64-bit Linux is a very rare target in my
daily routine) just because it is shorter. And I don't care that it is formally "incorrect" on my more common targets. Or may be not
"formally", but both gcc and clang think so.
Michael S <already5chosen@yahoo.com> writes:
On Wed, 07 Jan 2026 13:28:45 -0800
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
scott@slp53.sl.home (Scott Lurndal) writes:
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
Michael S <already5chosen@yahoo.com> writes:
On Tue, 6 Jan 2026 10:31:41 -0500
James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
On 2026-01-06 04:29, Michael S wrote:
On Tue, 6 Jan 2026 00:27:04 -0000 (UTC)...
Lawrence D’Oliveiro <ldo@nz.invalid> wrote:
Section 7.8 of the C spec defines macros you can use so you
don’t have to hard-code assumptions about the lengths of
integers in printf-format strings.
Did you ever try to use them? They look ugly.
Which is more important, correctness or beauty?
It depends.
When I know for sure that incorrectness has no consequences,
like in case of using %u to print 'unsigned long' on target
with 32-bit longs, or like using %llu to print 'unsigned long'
on target with 64-bit longs, then beauty wins. Easily.
Seriously?
An example:
unsigned long n = 42;
printf("%u\n", n); // incorrect
printf("%lu\n", n); // correct
Are you really saying that the second version is so much uglier
than the first that you'd rather write incorrect code?
I suspect he may have been referring to code that needs
to build for both 32-bit and 64-bit targets. One might
typedef 'uint64' to be unsigned long long on both targets
and just use %llu for the format string. BTDT.
In the quoted paragraph above, Michael wrote about using %u to
print unsigned long, not about using %u to print some type hidden
behind a typedef. If he didn't mean that, he can say so.
But even if he meant to talk about printing, say, uint64_t values,
my point stands.
I wouldn't define my own "uint64" type. I'd just use "uint64_t",
defined in <stdint.h>. And I'd use one of several *correct* ways
to print uint64_t values.
Michael, if you'd care to clarify, given:
unsigned long n = 42;
printf("%u\n", n); // incorrect
printf("%lu\n", n); // correct
(and assuming that unsigned int and unsigned long are the same
width on the current implementation), do you really prefer the
version marked as "incorrect"?
I hoped that I already clarified that point more than one time.
Obviously, I hoped wrong.
And you still haven't. I asked a specific question above. What is
your answer? Would you use a "%u" format to print a value that's
defined with type unsigned long? I inferred from what you wrote
that your answer would be yes. If your answer is no, I'll gladly
accept that. (And if so, what you wrote previously was unclear,
but I'm not going to worry about that if you clarify what you meant)
You've previously indicated that you find "%lu" uglier than "%u",
and that that's relevant to which one you would use. Do you still
think so?
I would appreciate direct yes or no answers to both of those
questions.
In the case I am talking about n declared as uint32_t.
uint32_t is an alias of 'unsigned long' on 32-bit embedded targets,
on 32-bit Linux, on 32-bit Windows and on 64-bit Windows. It is
alias of 'unsigned int' on 64-bit Linux.
Sometimes I move code between targets by myself, sometimes, rarely,
other people do it. I don't want to have different versions of the
code and I don't want to use ugly standard specifiers. Between two
pretty and working variants I prefer the shorter one. Partly
because it is guaranteed to work correctly on all my targets,
including LIN64, but more importantly (in practice, 64-bit Linux is
a very rare target in my daily routine) just because it is shorter.
And I don't care that it is formally "incorrect" on my more common
targets. Or may be not "formally", but both gcc and clang think so.
So you'd write code that happens to work on some implementations
rather than code that's correct on all implementations.
You know that unsigned long is at least 32 bits wide, and therefore
that converting a uint32_t value to unsigned long will not lose
information, and therefore that
uint32_t x = 42;
printf("%lu\n", (unsigned long)x);
will work correctly. You can do this without using the ugly
<inttypes.h> macros. Why wouldn't you?
Sure, you can write code that happens to work on the only
implementation you care about, but in my opinion, aside from being
dangerous, it's just too much work. I don't care whether uint32_t is
defined as unsigned int or unsigned long on a particular
implementation, and I don't have to care.
On Wed, 07 Jan 2026 16:00:19 -0800[...]
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
Michael S <already5chosen@yahoo.com> writes:
On Wed, 07 Jan 2026 13:28:45 -0800
Michael, if you'd care to clarify, given:
unsigned long n = 42;
printf("%u\n", n); // incorrect
printf("%lu\n", n); // correct
(and assuming that unsigned int and unsigned long are the same
width on the current implementation), do you really prefer the
version marked as "incorrect"?
I hoped that I already clarified that point more than one time.
Obviously, I hoped wrong.
And you still haven't. I asked a specific question above. What is
your answer? Would you use a "%u" format to print a value that's
defined with type unsigned long? I inferred from what you wrote
that your answer would be yes. If your answer is no, I'll gladly
accept that. (And if so, what you wrote previously was unclear,
but I'm not going to worry about that if you clarify what you meant)
When n declared as 'unsigned long' derectly rather than via unint32_t
alias than the answer is 'no'.
You've previously indicated that you find "%lu" uglier than "%u",
and that that's relevant to which one you would use. Do you still
think so?
I would appreciate direct yes or no answers to both of those
questions.
It depends on how n declared.
When it declared as 'unsigned long' then "lu" is not uglier.
When it is defined as uint32_t it is uglier, despite the fact that on absolute majority of the targets that I care about the latter is an
alias of the former.
Let me see if I understand you correctly.
uint32_t n = 42;
printf("%u\n", n);
printf("%lu\n", n);
In this context, you find "%lu" uglier than "%u"?
On Wed, 07 Jan 2026 13:28:45 -0800
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
scott@slp53.sl.home (Scott Lurndal) writes:
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
Michael S <already5chosen@yahoo.com> writes:
On Tue, 6 Jan 2026 10:31:41 -0500
James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
On 2026-01-06 04:29, Michael S wrote:
On Tue, 6 Jan 2026 00:27:04 -0000 (UTC)...
Lawrence D’Oliveiro <ldo@nz.invalid> wrote:
Section 7.8 of the C spec defines macros you can use so you
don’t have to hard-code assumptions about the lengths of >>>>>>>> integers in printf-format strings.
Did you ever try to use them? They look ugly.
Which is more important, correctness or beauty?
It depends.
When I know for sure that incorrectness has no consequences, like
in case of using %u to print 'unsigned long' on target with 32-bit
longs, or like using %llu to print 'unsigned long' on target with
64-bit longs, then beauty wins. Easily.
Seriously?
An example:
unsigned long n = 42;
printf("%u\n", n); // incorrect
printf("%lu\n", n); // correct
Are you really saying that the second version is so much uglier
than the first that you'd rather write incorrect code?
I suspect he may have been referring to code that needs
to build for both 32-bit and 64-bit targets. One might
typedef 'uint64' to be unsigned long long on both targets
and just use %llu for the format string. BTDT.
In the quoted paragraph above, Michael wrote about using %u to print
unsigned long, not about using %u to print some type hidden behind
a typedef. If he didn't mean that, he can say so.
But even if he meant to talk about printing, say, uint64_t values,
my point stands.
I wouldn't define my own "uint64" type. I'd just use "uint64_t",
defined in <stdint.h>. And I'd use one of several *correct* ways
to print uint64_t values.
Michael, if you'd care to clarify, given:
unsigned long n = 42;
printf("%u\n", n); // incorrect
printf("%lu\n", n); // correct
(and assuming that unsigned int and unsigned long are the same width
on the current implementation), do you really prefer the version
marked as "incorrect"?
I hoped that I already clarified that point more than one time.
Obviously, I hoped wrong.
In the case I am talking about n declared as uint32_t.
uint32_t is an alias of 'unsigned long' on 32-bit embedded targets, on
32-bit Linux, on 32-bit Windows and on 64-bit Windows. It is
alias of 'unsigned int' on 64-bit Linux.
Sometimes I move code between targets by myself, sometimes, rarely,
other people do it. I don't want to have different versions of the code
and I don't want to use ugly standard specifiers. Between two pretty
and working variants I prefer the shorter one. Partly because it is guaranteed to work correctly on all my targets, including LIN64, but
more importantly (in practice, 64-bit Linux is a very rare target in my
daily routine) just because it is shorter. And I don't care that it is formally "incorrect" on my more common targets. Or may be not
"formally", but both gcc and clang think so.
On 07/01/2026 00:44, James Kuyper wrote:
On 2026-01-06 13:05, Michael S wrote:
in case of using %u to print 'unsigned long' on target with 32-bit
longs, or like using %llu to print 'unsigned long' on target with
64-bit longs, then beauty wins. Easily.
You've got it backwards. "%u" is the correct specifier to use for
unsigned long on all platforms, whether unsigned long is 32, 36, or even
48 bits.
So not "%lu"?
On 2026-01-07 08:06, Tim Rentsch wrote:
scott@slp53.sl.home (Scott Lurndal) writes:
Michael S <already5chosen@yahoo.com> writes:
On Tue, 6 Jan 2026 10:31:41 -0500
James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
If you know that an expression has one of the standard-named types or >>>>> typedefs for with there is a corresponding printf() specifier, you
should use that specifier. Otherwise, if you know that an expression >>>>> has one of the types declared in <stdint.h>, you should use the
corresponding macro #defined in <inttypes.h> to print it.
I should? Really?
Sorry, James, but you have no authority to make such statements.
James is paraphrasing the C standard.
Really? What passage in the C standard is being paraphrased?
This is advice, not paraphrased text from the C standard. [...]
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> writes:
On 2026-01-07 08:06, Tim Rentsch wrote:
scott@slp53.sl.home (Scott Lurndal) writes:
I was responding to Scotty Lurndal's statement that the C
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> writes:
On 2026-01-07 08:06, Tim Rentsch wrote:
scott@slp53.sl.home (Scott Lurndal) writes:
Michael S <already5chosen@yahoo.com> writes:
On Tue, 6 Jan 2026 10:31:41 -0500
James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
If you know that an expression has one of the standard-named types or >>>>>> typedefs for with there is a corresponding printf() specifier, you >>>>>> should use that specifier. Otherwise, if you know that an expression >>>>>> has one of the types declared in <stdint.h>, you should use the
corresponding macro #defined in <inttypes.h> to print it.
I should? Really?
Sorry, James, but you have no authority to make such statements.
James is paraphrasing the C standard.
Really? What passage in the C standard is being paraphrased?
This is advice, not paraphrased text from the C standard. [...]
I was responding to Scotty Lurndal's statement that the C
standard was being paraphrased (by someone, it didn't matter to
me who). I don't care about whether his statement is true; my
interest is only in what part of the C standard he thinks is
being paraphrased. He is in a position to answer that question,
and more to the point he is the only person who is.
Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> writes:
On 2026-01-07 08:06, Tim Rentsch wrote:
scott@slp53.sl.home (Scott Lurndal) writes:
Michael S <already5chosen@yahoo.com> writes:
On Tue, 6 Jan 2026 10:31:41 -0500
James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
If you know that an expression has one of the standard-named
types or typedefs for with there is a corresponding printf()
specifier, you should use that specifier. Otherwise, if you
know that an expression has one of the types declared in
<stdint.h>, you should use the corresponding macro #defined in
<inttypes.h> to print it.
I should? Really?
Sorry, James, but you have no authority to make such statements.
James is paraphrasing the C standard.
Really? What passage in the C standard is being paraphrased?
This is advice, not paraphrased text from the C standard. [...]
I was responding to Scotty Lurndal's statement that the C
standard was being paraphrased (by someone, it didn't matter to
me who). I don't care about whether his statement is true; my
interest is only in what part of the C standard he thinks is
being paraphrased. He is in a position to answer that question,
and more to the point he is the only person who is.
It's pretty clear that the standard describes the printf
function and the methods used to match the format characters
to the data types of the arguments. The fact that James
framed that as advice doesn't change interpretation of
the text of the standard, whether or not you consider
that to be a paraphrase.
"The main rules for paraphrasing are to fully understand the
original text, restate its core idea in your own words and
sentence structure, use synonyms, and always cite the original
source to avoid plagiarism, even if the wording is different.
And it is spelled "Scott".
Lawrence D’Oliveiro <ldo@nz.invalid> writes:
On Wed, 7 Jan 2026 12:45:30 -0500, James Russell Kuyper Jr. wrote:
On a different point, I used time_t as an example. It would have
been better to use ptrdiff_t instead, since <inttypes.h> has a macro
for that type, and doesn't have one for time_t.
This is why you have configure scripts, so they can figure out the
right types to use for building on your platform.
I don't follow. ptrdiff_t is defined in <stddef.h>, and is the correct
type for the result of subtracting two pointers. What relevant
information would a configure script give you?
On Wed, 07 Jan 2026 16:00:19 -0800...
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
So you'd write code that happens to work on some implementations
rather than code that's correct on all implementations.
No, it is correct on all implementation. Idea that in C, as opposed to
C++, two unsigned integer types of the same size are somehow
different is, IMHO, an abomination. And that is one not especially
common case in which I don't care about opinion of the Standard.
I also don't care. Since for more than decade* I didn't have target
with 'int' shorter than 32 bits, I just use %u. It takes me zero
thinking.
BTW, I am always aware of exact sizes of the basic types of the target
that I work on. I don't feel comfotable without such knowledge. That
how my mind works. It has problems with too abstract abstractions.
No, it is correct on all implementation. Idea that in C, as opposed to
C++, two unsigned integer types of the same size are somehow
different is, IMHO, an abomination. And that is one not especially
common case in which I don't care about opinion of the Standard.
On 2026-01-07 19:38, Michael S wrote:
On Wed, 07 Jan 2026 16:00:19 -0800...
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
So you'd write code that happens to work on some implementations
rather than code that's correct on all implementations.
No, it is correct on all implementation. Idea that in C, as opposed
to C++, two unsigned integer types of the same size are somehow
different is, IMHO, an abomination. And that is one not especially
common case in which I don't care about opinion of the Standard.
We're not talking about two unsigned integer types with same size.
We're talking about unsigned long, which can be any size >= 32 bits,
and uint32_t, which can only be exactly 32 bits. Your code is NOT
portable to a platform where unsigned long is greater than 32 bits.
...
I also don't care. Since for more than decade* I didn't have target
with 'int' shorter than 32 bits, I just use %u. It takes me zero
thinking.
As a general rule, I find that people who claim a decision requires
no thought generally are referring to a decision that should have
been made differently if sufficient thought had been put into it.
This is a prime example.
BTW, I am always aware of exact sizes of the basic types of the
target that I work on. I don't feel comfotable without such
knowledge. That how my mind works. It has problems with too
abstract abstractions.
I'd have no problem with your approach if you hadn't falsely claimed
that "It is correct on all platforms".
There's nothing wrong with
code that is intentionally platform specific. Platform-specific code
that the author incorrectly believes to be "correct on all platforms"
is a problem.
On Thu, 8 Jan 2026 19:31:13 -0500
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
On 2026-01-07 19:38, Michael S wrote:
On Wed, 07 Jan 2026 16:00:19 -0800...
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
So you'd write code that happens to work on some implementations
rather than code that's correct on all implementations.
No, it is correct on all implementation. Idea that in C, as opposed
to C++, two unsigned integer types of the same size are somehow
different is, IMHO, an abomination. And that is one not especially
common case in which I don't care about opinion of the Standard.
We're not talking about two unsigned integer types with same size.
We're talking about unsigned long, which can be any size >= 32 bits,
and uint32_t, which can only be exactly 32 bits. Your code is NOT
portable to a platform where unsigned long is greater than 32 bits.
I don't know how you came to discussions of what is possible.
My statement was concrete. It was about platforms like Windows (of all flavors) and 2-3 specific 32-bit embedded targets that I currently
care about.
On all these platforms uint32_t is alias of 'unsigned long' which is
32-bit wide. 'unsigned int' is also 32-bit wide.
I claim that *on these platforms* uint32_t and 'unsigned int' are *not* different types. I don't care to what the Standard says about it.
I do care about what gcc says about it because I am annoyed by warnings
that I consider pointless.
Printing uint32_t values on these platforms with %u specifier, apart
from advantage of being shorter, has advantage of being undoubtedly
correct on LIN64. Unlike printing with %lu.
On 09/01/2026 13:18, Michael S wrote:
On Thu, 8 Jan 2026 19:31:13 -0500
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
On 2026-01-07 19:38, Michael S wrote:
On Wed, 07 Jan 2026 16:00:19 -0800...
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
So you'd write code that happens to work on some implementations rather than code that's correct on all implementations.
No, it is correct on all implementation. Idea that in C, as opposed
to C++, two unsigned integer types of the same size are somehow different is, IMHO, an abomination. And that is one not especially common case in which I don't care about opinion of the Standard.
We're not talking about two unsigned integer types with same size.
We're talking about unsigned long, which can be any size >= 32 bits,
and uint32_t, which can only be exactly 32 bits. Your code is NOT portable to a platform where unsigned long is greater than 32 bits.
I don't know how you came to discussions of what is possible.
My statement was concrete. It was about platforms like Windows (of all flavors) and 2-3 specific 32-bit embedded targets that I currently
care about.
On all these platforms uint32_t is alias of 'unsigned long' which is
32-bit wide. 'unsigned int' is also 32-bit wide.
I claim that *on these platforms* uint32_t and 'unsigned int' are *not* different types. I don't care to what the Standard says about it.
Of course they are different types. In C, "unsigned int" and "unsigned long" are different types. The standard says so - and it is the
standard that defines the language.
I do care about what gcc says about it because I am annoyed by warnings that I consider pointless.
The warnings are not pointless, despite what you might think. And of course gcc is not going to modify its warnings to pander to someone who
has their own personal ideas about what C should be. We /all/ have
ideas about how C could be better for our own needs. But outside the
realm of personal languages where the single user also designed the
language and wrote the compiler, you have to work with the language as
it is defined.
For a clear example of the differences between unsigned int and unsigned long, look at the generated code here:
<https://godbolt.org/z/hdjz6Y7vY>
That is for embedded 32-bit ARM, where "uint32_t" is "unsigned long",
and is the same size as "unsigned int". Then try swapping the compiler
to the 32-bit ARM gcc Linux version - here "uint32_t" is "unsigned int",
and again the same size as "unsigned long". Look at the differences in
the code.
It doesn't matter if /you/ think that all 32-bit integer types should be
the same - in C, they are not. And therefore in C compilers, they are
not the same.
Snipet from ClassGuidelines.txtPrinting uint32_t values on these platforms with %u specifier, apart
from advantage of being shorter, has advantage of being undoubtedly
correct on LIN64. Unlike printing with %lu.
But printing uint32_t with "%u" on 32-bit EABI ARM is not correct - it
is UB. It will /probably/ work, but maybe some day you will come across
a situation where it will not.
I have a lot of trouble understanding why you would go out of your way
to knowingly write incorrect code - prioritising tiny, irrelevant
savings in source code space over correct, guaranteed, portable code
that can be automatically checked by tools.
scott@slp53.sl.home (Scott Lurndal) writes:
Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
I was responding to Scotty Lurndal's statement that the C
standard was being paraphrased (by someone, it didn't matter to
me who). I don't care about whether his statement is true; my
interest is only in what part of the C standard he thinks is
being paraphrased. He is in a position to answer that question,
and more to the point he is the only person who is.
It's pretty clear that the standard describes the printf
function and the methods used to match the format characters
to the data types of the arguments. The fact that James
framed that as advice doesn't change interpretation of
the text of the standard, whether or not you consider
that to be a paraphrase.
"The main rules for paraphrasing are to fully understand the
original text, restate its core idea in your own words and
sentence structure, use synonyms, and always cite the original
source to avoid plagiarism, even if the wording is different.
I see where the C standard says the macros in inttypes.h are
suitable for use with printf (and scanf). That isn't at all
the same as saying people should use them.
Just because
something can be done doesn't mean it should be done.
On Fri, 2026-01-09 at 13:49 +0100, David Brown wrote:[...]
[SNIP]I have a lot of trouble understanding why you would go out of your way
to knowingly write incorrect code - prioritising tiny, irrelevant
savings in source code space over correct, guaranteed, portable code
that can be automatically checked by tools.
Snipet from ClassGuidelines.txt
...
wrd(or notation)
This function converts the argument object (a type, class,..) to text
My reply might not be directly in the topic of current post. Just jumpped in to reply.
It looks to me the format character MUST match the type passed to printf, otherwise UB.
On Thu, 8 Jan 2026 19:31:13 -0500...
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
I'd have no problem with your approach if you hadn't falsely claimed
that "It is correct on all platforms".
Which I didn't.
No, it is correct on all implementation.
Tim Rentsch <tr.17687@z991.linuxsc.com> writes:...
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> writes:
On 2026-01-07 08:06, Tim Rentsch wrote:
scott@slp53.sl.home (Scott Lurndal) writes:
Michael S <already5chosen@yahoo.com> writes:
On Tue, 6 Jan 2026 10:31:41 -0500
James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
If you know that an expression has one of the standard-named types or >>>>>>> typedefs for with there is a corresponding printf() specifier, you >>>>>>> should use that specifier. Otherwise, if you know that an expression >>>>>>> has one of the types declared in <stdint.h>, you should use the
corresponding macro #defined in <inttypes.h> to print it.
...James is paraphrasing the C standard.
It's pretty clear that the standard describes the printf
function and the methods used to match the format characters
to the data types of the arguments. The fact that James
framed that as advice doesn't change interpretation of
the text of the standard, whether or not you consider
that to be a paraphrase.
On 2026-01-09 07:18, Michael S wrote:
On Thu, 8 Jan 2026 19:31:13 -0500...
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
I'd have no problem with your approach if you hadn't falsely
claimed that "It is correct on all platforms".
Which I didn't.
On 2026-01-07 19:38, Michael S wrote:
...
No, it is correct on all implementation.
On Sat, 10 Jan 2026 22:02:03 -0500
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
On 2026-01-09 07:18, Michael S wrote:
On Thu, 8 Jan 2026 19:31:13 -0500...
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
I'd have no problem with your approach if you hadn't falsely
claimed that "It is correct on all platforms".
Which I didn't.
On 2026-01-07 19:38, Michael S wrote:
...
No, it is correct on all implementation.
The quote is taken out of context.
The context was that on platforms that have properties (a) and (b) (see below) printing variables declared as uint32_t via %u is probably UB according to the Standard (I don't know for sure, however it is
probable),
but it can't cause troubles with production C compiler. Or
with any C compiler that is made in intention of being used rather than crafted to prove theoretical points.
Properties are:
a) uint32_t aliased to 'unsigned long'
b) 'unsigned int' is at least 32-bit wide.
I never claimed that it is good idea on targets with 'unsigned int'
that is narrower.
Michael S <already5chosen@yahoo.com> writes:
On Sat, 10 Jan 2026 22:02:03 -0500
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
On 2026-01-09 07:18, Michael S wrote:
On Thu, 8 Jan 2026 19:31:13 -0500...
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
wrote:
I'd have no problem with your approach if you hadn't falsely
claimed that "It is correct on all platforms".
Which I didn't.
On 2026-01-07 19:38, Michael S wrote:
...
No, it is correct on all implementation.
The quote is taken out of context.
The context was that on platforms that have properties (a) and (b)
(see below) printing variables declared as uint32_t via %u is
probably UB according to the Standard (I don't know for sure,
however it is probable),
I'm sure. uint32_t is an alias for some predefined integer type.
This:
uint32_t n = 42;
printf("%u\n", n);
has undefined behavior *unless* uint32_t happens to an alias for
unsigned int in the current implementation -- not just any 32-bit
unsigned integer type, only unsigned int.
If uint32_t is an alias for unsigned long (which implies that
unsigned long is exactly 32 bits), then the call's behavior is
undefined. (It might happen to "work".)
If uint32_t and unsigned long have different sizes, it still might
happen happen to "work", depending on calling conventions. Passing a
32-bit argument and telling printf to expect a 64-bit value clearly
has undefined behavior, but perhaps both happen to be passed in 64-bit registers, for example.
but it can't cause troubles with production C compiler.
Or with any C compiler that is made in intention of being used
rather than crafted to prove theoretical points.
Properties are:
a) uint32_t aliased to 'unsigned long'
Not guaranteed by the language (and not true on the implementations
I use most often).
b) 'unsigned int' is at least 32-bit wide.
Not guaranteed by the language (though it happens to be guaranteed by
POSIX).
I never claimed that it is good idea on targets with 'unsigned int'
that is narrower.
I claim that it's not a good idea on any target.
I find it *much* easier to write portable code than to spend time
figuring out what non-portable code will happens to work on the
platforms I happen to care about today.
uint32_t n = 42;
printf("%lu\n", (unsigned long)n);
unsigned long is guaranteed by the language to be at least 32 bits.
The conversion is guaranteed not to lose information. The format
matches the type of the argument. And the code will work correctly
on any conforming hosted implementation. (It might involve an
unnecessary 32 to 64 bit conversion, but given the overhead of
printf, that's unlikely to be a problem -- and if it is, I can use
the appropriate macro from <inttypes.h>.)
And to my eyes, using "%u" with a uint32_t argument is *ugly*.
On Sun, 11 Jan 2026 04:59:47 -0800
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
Michael S <already5chosen@yahoo.com> writes:
On Sat, 10 Jan 2026 22:02:03 -0500
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
On 2026-01-09 07:18, Michael S wrote:
On Thu, 8 Jan 2026 19:31:13 -0500...
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
wrote:
I'd have no problem with your approach if you hadn't falsely
claimed that "It is correct on all platforms".
Which I didn't.
On 2026-01-07 19:38, Michael S wrote:
...
> No, it is correct on all implementation.
The quote is taken out of context.
The context was that on platforms that have properties (a) and (b)
(see below) printing variables declared as uint32_t via %u is
probably UB according to the Standard (I don't know for sure,
however it is probable),
I'm sure. uint32_t is an alias for some predefined integer type.
This:
uint32_t n = 42;
printf("%u\n", n);
has undefined behavior *unless* uint32_t happens to an alias for
unsigned int in the current implementation -- not just any 32-bit
unsigned integer type, only unsigned int.
If uint32_t is an alias for unsigned long (which implies that
unsigned long is exactly 32 bits), then the call's behavior is
undefined. (It might happen to "work".)
What exactly, assuming that conditions (a) and (b) fulfilled, should implementation do to prevent it from working?
I mean short of completely crazy things that will make maintainer
immediately fired?
If uint32_t and unsigned long have different sizes, it still might
happen happen to "work", depending on calling conventions. Passing a
32-bit argument and telling printf to expect a 64-bit value clearly
has undefined behavior, but perhaps both happen to be passed in 64-bit
registers, for example.
And that is sort of intimate knowledge of the ABI that I don't want to exploit, as already mentioned in my other post in this sub-thread.
On 11/01/2026 14:32, Michael S wrote:
On Sun, 11 Jan 2026 04:59:47 -0800
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
Michael S <already5chosen@yahoo.com> writes:
On Sat, 10 Jan 2026 22:02:03 -0500
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
wrote:
On 2026-01-09 07:18, Michael S wrote:
On Thu, 8 Jan 2026 19:31:13 -0500...
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
wrote:
I'd have no problem with your approach if you hadn't falsely
claimed that "It is correct on all platforms".
Which I didn't.
On 2026-01-07 19:38, Michael S wrote:
...
> No, it is correct on all implementation.
The quote is taken out of context.
The context was that on platforms that have properties (a) and (b)
(see below) printing variables declared as uint32_t via %u is
probably UB according to the Standard (I don't know for sure,
however it is probable),
I'm sure. uint32_t is an alias for some predefined integer type.
This:
uint32_t n = 42;
printf("%u\n", n);
has undefined behavior *unless* uint32_t happens to an alias for
unsigned int in the current implementation -- not just any 32-bit
unsigned integer type, only unsigned int.
If uint32_t is an alias for unsigned long (which implies that
unsigned long is exactly 32 bits), then the call's behavior is
undefined. (It might happen to "work".)
What exactly, assuming that conditions (a) and (b) fulfilled, should implementation do to prevent it from working?
I mean short of completely crazy things that will make maintainer immediately fired?
If an architecture has 32-bit "unsigned long", then "unsigned int" is necessarily also 32-bit (since "unsigned int" is always at least
32-bit,
and "unsigned long" cannot be smaller than "unsigned int").
The very fact that you listed "unsigned int is at least 32-bit wide"
as an assumption shows you are not well versed with the basics of C
standards in this area.
On Sun, 11 Jan 2026 16:34:28 +0100
David Brown <david.brown@hesbynett.no> wrote:
On 11/01/2026 14:32, Michael S wrote:
On Sun, 11 Jan 2026 04:59:47 -0800
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
Michael S <already5chosen@yahoo.com> writes:
On Sat, 10 Jan 2026 22:02:03 -0500
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
wrote:
On 2026-01-09 07:18, Michael S wrote:
On Thu, 8 Jan 2026 19:31:13 -0500...
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
wrote:
I'd have no problem with your approach if you hadn't falsely
claimed that "It is correct on all platforms".
Which I didn't.
On 2026-01-07 19:38, Michael S wrote:
...
> No, it is correct on all implementation.
The quote is taken out of context.
The context was that on platforms that have properties (a) and (b)
(see below) printing variables declared as uint32_t via %u is
probably UB according to the Standard (I don't know for sure,
however it is probable),
I'm sure. uint32_t is an alias for some predefined integer type.
This:
uint32_t n = 42;
printf("%u\n", n);
has undefined behavior *unless* uint32_t happens to an alias for
unsigned int in the current implementation -- not just any 32-bit
unsigned integer type, only unsigned int.
If uint32_t is an alias for unsigned long (which implies that
unsigned long is exactly 32 bits), then the call's behavior is
undefined. (It might happen to "work".)
What exactly, assuming that conditions (a) and (b) fulfilled, should
implementation do to prevent it from working?
I mean short of completely crazy things that will make maintainer
immediately fired?
If an architecture has 32-bit "unsigned long", then "unsigned int" is
necessarily also 32-bit (since "unsigned int" is always at least
32-bit,
I am pretty sure that it is wrong.
C Standard does not require for 'unsigned int' to be above 16 bits.
and "unsigned long" cannot be smaller than "unsigned int").
The very fact that you listed "unsigned int is at least 32-bit wide"
as an assumption shows you are not well versed with the basics of C
standards in this area.
I am not well versed in the Standard. But in this particular case you
are the one who doesn't know it.
On Sat, 10 Jan 2026 22:02:03 -0500
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
On 2026-01-09 07:18, Michael S wrote:
On Thu, 8 Jan 2026 19:31:13 -0500
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
...
I'd have no problem with your approach if you hadn't falsely
claimed that "It is correct on all platforms".
Which I didn't.
On 2026-01-07 19:38, Michael S wrote:
...
No, it is correct on all implementation.
The quote is taken out of context.
The context was that on platforms that have properties (a) and (b) (see below) printing variables declared as uint32_t via %u is probably UB according to the Standard (I don't know for sure, however it is
probable), but it can't cause troubles with production C compiler. Or
with any C compiler that is made in intention of being used rather than crafted to prove theoretical points.
Properties are:
a) uint32_t aliased to 'unsigned long'
b) 'unsigned int' is at least 32-bit wide.
Michael S <already5chosen@yahoo.com> writes:
On Sat, 10 Jan 2026 22:02:03 -0500
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
On 2026-01-09 07:18, Michael S wrote:
On Thu, 8 Jan 2026 19:31:13 -0500
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
...
I'd have no problem with your approach if you hadn't falsely
claimed that "It is correct on all platforms".
Which I didn't.
On 2026-01-07 19:38, Michael S wrote:
...
No, it is correct on all implementation.
The quote is taken out of context.
The context was that on platforms that have properties (a) and (b) (see
below) printing variables declared as uint32_t via %u is probably UB
according to the Standard (I don't know for sure, however it is
probable),
I'm sure. uint32_t is an alias for some predefined integer type.
Michael S <already5chosen@yahoo.com> writes:
On Sat, 10 Jan 2026 22:02:03 -0500
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
On 2026-01-09 07:18, Michael S wrote:
On Thu, 8 Jan 2026 19:31:13 -0500
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
wrote:
...
I'd have no problem with your approach if you hadn't falsely
claimed that "It is correct on all platforms".
Which I didn't.
On 2026-01-07 19:38, Michael S wrote:
...
No, it is correct on all implementation.
The quote is taken out of context.
The context was that on platforms that have properties (a) and (b)
(see below) printing variables declared as uint32_t via %u is
probably UB according to the Standard (I don't know for sure,
however it is probable), but it can't cause troubles with
production C compiler. Or with any C compiler that is made in
intention of being used rather than crafted to prove theoretical
points. Properties are:
a) uint32_t aliased to 'unsigned long'
b) 'unsigned int' is at least 32-bit wide.
It seems unlikely that any implementation would make such a
choice. Can you name one that does?
On Sun, 11 Jan 2026 04:59:47 -0800
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
Michael S <already5chosen@yahoo.com> writes:
On Sat, 10 Jan 2026 22:02:03 -0500
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
On 2026-01-09 07:18, Michael S wrote:
On Thu, 8 Jan 2026 19:31:13 -0500...
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
wrote:
I'd have no problem with your approach if you hadn't falsely
claimed that "It is correct on all platforms".
Which I didn't.
On 2026-01-07 19:38, Michael S wrote:
...
No, it is correct on all implementation.
The quote is taken out of context.
The context was that on platforms that have properties (a) and (b)
(see below) printing variables declared as uint32_t via %u is
probably UB according to the Standard (I don't know for sure,
however it is probable),
I'm sure. uint32_t is an alias for some predefined integer type.
This:
uint32_t n = 42;
printf("%u\n", n);
has undefined behavior *unless* uint32_t happens to an alias for
unsigned int in the current implementation -- not just any 32-bit
unsigned integer type, only unsigned int.
If uint32_t is an alias for unsigned long (which implies that
unsigned long is exactly 32 bits), then the call's behavior is
undefined. (It might happen to "work".)
What exactly, assuming that conditions (a) and (b) fulfilled, should implementation do to prevent it from working?
I mean short of completely crazy things that will make maintainer
immediately fired?
If uint32_t and unsigned long have different sizes, it still might
happen happen to "work", depending on calling conventions. Passing a
32-bit argument and telling printf to expect a 64-bit value clearly
has undefined behavior, but perhaps both happen to be passed in 64-bit
registers, for example.
And that is sort of intimate knowledge of the ABI that I don't want to exploit, as already mentioned in my other post in this sub-thread.
but it can't cause troubles with production C compiler.
Or with any C compiler that is made in intention of being used
rather than crafted to prove theoretical points.
Properties are:
a) uint32_t aliased to 'unsigned long'
Not guaranteed by the language (and not true on the implementations
I use most often).
Did I ever say that it is guaranteed by the language or that it is
universal in any other way?
Normally you have much better reading comprehension than one that you demonstrate in this discussion. I'd guess that it's because I somehow
caused you to become angry.
I never claimed that it is good idea on targets with 'unsigned int'
that is narrower.
I claim that it's not a good idea on any target.
I find it *much* easier to write portable code than to spend time
figuring out what non-portable code will happens to work on the
platforms I happen to care about today.
uint32_t n = 42;
printf("%lu\n", (unsigned long)n);
unsigned long is guaranteed by the language to be at least 32 bits.
The conversion is guaranteed not to lose information. The format
matches the type of the argument. And the code will work correctly
on any conforming hosted implementation. (It might involve an
unnecessary 32 to 64 bit conversion, but given the overhead of
printf, that's unlikely to be a problem -- and if it is, I can use
the appropriate macro from <inttypes.h>.)
And to my eyes, using "%u" with a uint32_t argument is *ugly*.
To be fair, it is not ideal.
The solution that I would prefer would be universal adaption of
Microsoft's size specifiers I32 and I64. They are not going to win
beauty competition, but in practice they are a lot more convenient to
use than standard macros and are equally good at carrying programmer's intentions.
Microsoft has strong influence in committee, but was not able to push
it into C11, where they successfully forced hands of other members on
few much bigger and more controversial issues.
I don't know what it means, May be, there is bold technical reason
behind non-standardization of these size specifiers. Or may be there is
no reason and Microsoft simply never tried.
On Sun, 11 Jan 2026 04:59:47 -0800
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
Michael S <already5chosen@yahoo.com> writes:
On Sat, 10 Jan 2026 22:02:03 -0500
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
On 2026-01-09 07:18, Michael S wrote:
On Thu, 8 Jan 2026 19:31:13 -0500...
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
wrote:
I'd have no problem with your approach if you hadn't falsely
claimed that "It is correct on all platforms".
Which I didn't.
On 2026-01-07 19:38, Michael S wrote:
...
No, it is correct on all implementation.
The quote is taken out of context.
The context was that on platforms that have properties (a) and (b)
(see below) printing variables declared as uint32_t via %u is
probably UB according to the Standard (I don't know for sure,
however it is probable),
I'm sure. uint32_t is an alias for some predefined integer type.
This:
uint32_t n = 42;
printf("%u\n", n);
has undefined behavior *unless* uint32_t happens to an alias for
unsigned int in the current implementation -- not just any 32-bit
unsigned integer type, only unsigned int.
If uint32_t is an alias for unsigned long (which implies that
unsigned long is exactly 32 bits), then the call's behavior is
undefined. (It might happen to "work".)
What exactly, assuming that conditions (a) and (b) fulfilled, should implementation do to prevent it from working?
I mean short of completely crazy things that will make maintainer
immediately fired?
If uint32_t and unsigned long have different sizes, it still might
happen happen to "work", depending on calling conventions. Passing a
32-bit argument and telling printf to expect a 64-bit value clearly
has undefined behavior, but perhaps both happen to be passed in 64-bit
registers, for example.
And that is sort of intimate knowledge of the ABI that I don't want to exploit, as already mentioned in my other post in this sub-thread.
but it can't cause troubles with production C compiler.
Or with any C compiler that is made in intention of being used
rather than crafted to prove theoretical points.
Properties are:
a) uint32_t aliased to 'unsigned long'
Not guaranteed by the language (and not true on the implementations
I use most often).
Did I ever say that it is guaranteed by the language or that it is
universal in any other way?
Normally you have much better reading comprehension than one that you demonstrate in this discussion. I'd guess that it's because I somehow
caused you to become angry.
I never claimed that it is good idea on targets with 'unsigned int'
that is narrower.
I claim that it's not a good idea on any target.
I find it *much* easier to write portable code than to spend time
figuring out what non-portable code will happens to work on the
platforms I happen to care about today.
uint32_t n = 42;
printf("%lu\n", (unsigned long)n);
unsigned long is guaranteed by the language to be at least 32 bits.
The conversion is guaranteed not to lose information. The format
matches the type of the argument. And the code will work correctly
on any conforming hosted implementation. (It might involve an
unnecessary 32 to 64 bit conversion, but given the overhead of
printf, that's unlikely to be a problem -- and if it is, I can use
the appropriate macro from <inttypes.h>.)
And to my eyes, using "%u" with a uint32_t argument is *ugly*.
To be fair, it is not ideal.
The solution that I would prefer would be universal adaption of
Microsoft's size specifiers I32 and I64. They are not going to win
beauty competition, but in practice they are a lot more convenient to
use than standard macros and are equally good at carrying programmer's intentions.
Microsoft has strong influence in committee, but was not able to push
it into C11, where they successfully forced hands of other members on
few much bigger and more controversial issues.
I don't know what it means, May be, there is bold technical reason
behind non-standardization of these size specifiers. Or may be there is
no reason and Microsoft simply never tried.
Michael S <already5chosen@yahoo.com> writes:[...]
[...]I mean short of completely crazy things that will make maintainer
immediately fired?
Most likely nothing.
On Sun, 11 Jan 2026 11:51:43 -0800[...]
Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
Michael S <already5chosen@yahoo.com> writes:
Properties are:
a) uint32_t aliased to 'unsigned long'
b) 'unsigned int' is at least 32-bit wide.
It seems unlikely that any implementation would make such a
choice. Can you name one that does?
Four out of four target for which I write C programs for living in this decade:
- Altera Nios2 (nios2-elf-gcc)
- Arm Cortex-M bare metal (arm-none-eabi-gcc)
- Win32-i386, various compilers
- Win64-Amd64,various compilers
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.
Michael S <already5chosen@yahoo.com> writes:
The solution that I would prefer would be universal adaption of
Microsoft's size specifiers I32 and I64. They are not going to win
beauty competition, but in practice they are a lot more convenient to
use than standard macros and are equally good at carrying programmer's
intentions.
The relative beauty of a feature that isn't available hardly seems
relevant.
The ideal solution is to write correct, and preferably portable,
code in the first place. There are often good reasons to write
non-portable code, but I suggest that fiding the correct format
string to be ugly is not one of them.
(Microsoft's documentation says that "I32" prefix applies to an
argument of type __int32 or unsigned __int32. I don't know whether
__int32 is compatible with int, with long, or neither, and I don't
much care. I don't know what Microsoft guarantees about printf
with incompatible types that happen to have the same size.)
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
Michael S <already5chosen@yahoo.com> writes:
On Sat, 10 Jan 2026 22:02:03 -0500
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
On 2026-01-09 07:18, Michael S wrote:
On Thu, 8 Jan 2026 19:31:13 -0500
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
...
I'd have no problem with your approach if you hadn't falsely
claimed that "It is correct on all platforms".
Which I didn't.
On 2026-01-07 19:38, Michael S wrote:
...
No, it is correct on all implementation.
The quote is taken out of context.
The context was that on platforms that have properties (a) and (b) (see
below) printing variables declared as uint32_t via %u is probably UB
according to the Standard (I don't know for sure, however it is
probable),
I'm sure. uint32_t is an alias for some predefined integer type.
Very likely, but I don't think the C standard requires it. TTBOMU
the C standard allows the possibility of an implementation where
uint32_t is type distinct from any other nameable type, and yet
the implementation could still be conforming.
Michael S <already5chosen@yahoo.com> writes:
On Sun, 11 Jan 2026 11:51:43 -0800[...]
Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
Michael S <already5chosen@yahoo.com> writes:
Properties are:
a) uint32_t aliased to 'unsigned long'
b) 'unsigned int' is at least 32-bit wide.
It seems unlikely that any implementation would make such a
choice. Can you name one that does?
Four out of four target for which I write C programs for living in this
decade:
- Altera Nios2 (nios2-elf-gcc)
- Arm Cortex-M bare metal (arm-none-eabi-gcc)
- Win32-i386, various compilers
- Win64-Amd64,various compilers
I find that surprising. I just tried a test program that prints
the name of the type uint32_t is an alias for (using _Generic),
and it's alias to unsigned int on every implementation I tried.
(Your properties are limited to systems with 32-bit int and long.)
For an implementation where int and long are both 32 bits, it
wouldn't have surprised me for uint32_t to be an alias either for
unsigned int or for unsigned long, and I wouldn't care either way
beyond idle curiosity, but all the implementations I've tried choose
to use unsigned int.
Well, if I would be pedantic, then in this decade I also wrote several
programs for Arm32 Linux, where I don't know whether uint32_t is alias
of 'unsigned int' or 'unsigned long', few programs for AMD64 Linux,
where I know that uint32_t is an alias of 'unsigned long' and may be one
program for ARM64 Linux that is the same as AMD64 Linux.
But all those outliers together constitute a tiny fraction of the code
that I wrote recently.
One advantage of my approach is that I don't have to know or care
what the underlying type of uint32_t is.
On Sun, 11 Jan 2026 11:51:43 -0800
Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
Michael S <already5chosen@yahoo.com> writes:
On Sat, 10 Jan 2026 22:02:03 -0500
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
On 2026-01-09 07:18, Michael S wrote:
On Thu, 8 Jan 2026 19:31:13 -0500
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
wrote:
...
I'd have no problem with your approach if you hadn't falsely
claimed that "It is correct on all platforms".
Which I didn't.
On 2026-01-07 19:38, Michael S wrote:
...
No, it is correct on all implementation.
The quote is taken out of context.
The context was that on platforms that have properties (a) and (b)
(see below) printing variables declared as uint32_t via %u is
probably UB according to the Standard (I don't know for sure,
however it is probable), but it can't cause troubles with
production C compiler. Or with any C compiler that is made in
intention of being used rather than crafted to prove theoretical
points. Properties are:
a) uint32_t aliased to 'unsigned long'
b) 'unsigned int' is at least 32-bit wide.
It seems unlikely that any implementation would make such a
choice. Can you name one that does?
Four out of four target for which I write C programs for living in
this decade:
- Altera Nios2 (nios2-elf-gcc)
- Arm Cortex-M bare metal (arm-none-eabi-gcc)
- Win32-i386, various compilers
- Win64-Amd64,various compilers
Well, if I would be pedantic, then in this decade I also wrote several programs for Arm32 Linux, where I don't know whether uint32_t is alias
of 'unsigned int' or 'unsigned long', few programs for AMD64 Linux,
where I know that uint32_t is an alias of 'unsigned long'
and may be
one program for ARM64 Linux that is the same as AMD64 Linux.
But all those outliers together constitute a tiny fraction of the code
that I wrote recently.
Michael S <already5chosen@yahoo.com> writes:
To be fair, it is not ideal.
The solution that I would prefer would be universal adaption of
Microsoft's size specifiers I32 and I64. They are not going to win
beauty competition, but in practice they are a lot more convenient
to use than standard macros and are equally good at carrying
programmer's intentions.
The relative beauty of a feature that isn't available hardly seems
relevant.
The ideal solution is to write correct, and preferably portable,
code in the first place. There are often good reasons to write
non-portable code, but I suggest that fiding the correct format
string to be ugly is not one of them.
(Microsoft's documentation says that "I32" prefix applies to an
argument of type __int32 or unsigned __int32. I don't know whether
__int32 is compatible with int, with long, or neither, and I don't
much care. I don't know what Microsoft guarantees about printf
with incompatible types that happen to have the same size.)
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
Michael S <already5chosen@yahoo.com> writes:[...]
I mean short of completely crazy things that will make maintainer
immediately fired?
Most likely nothing.[...]
Sorry about the duplicate post (server problems).
On 11/01/2026 23:56, Keith Thompson wrote:
Michael S <already5chosen@yahoo.com> writes:
The solution that I would prefer would be universal adaption of
Microsoft's size specifiers I32 and I64. They are not going to win
beauty competition, but in practice they are a lot more convenient
to use than standard macros and are equally good at carrying
programmer's intentions.
The relative beauty of a feature that isn't available hardly seems relevant.
The ideal solution is to write correct, and preferably portable,
code in the first place. There are often good reasons to write non-portable code, but I suggest that fiding the correct format
string to be ugly is not one of them.
(Microsoft's documentation says that "I32" prefix applies to an
argument of type __int32 or unsigned __int32. I don't know whether
__int32 is compatible with int, with long, or neither, and I don't
much care. I don't know what Microsoft guarantees about printf
with incompatible types that happen to have the same size.)
C23 includes length specifiers with explicit bit counts, so "%w32u"
is for an unsigned integer argument of 32 bits:
"""
wN Specifies that a following b, B, d, i, o, u, x, or X conversion
specifier applies to an integer argument with a specific width where
N is a positive decimal integer with no leading zeros (the argument
will have been promoted according to the integer promotions, but its
value shall be converted to the unpromoted type); or that a following
n conversion specifier applies to a pointer to an integer type
argument with a width of N bits. All minimum-width integer types
(7.22.1.2) and exact-width integer types (7.22.1.1) defined in the
header <stdint.h> shall be supported. Other supported values of N are implementation-defined. """
That looks to me that it would be a correct specifier for uint32_t,
and should also be fully defined behaviour for unsigned int and
unsigned long if these are 32 bits wide.
Michael S <already5chosen@yahoo.com> writes:
On Sun, 11 Jan 2026 11:51:43 -0800[...]
Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
Michael S <already5chosen@yahoo.com> writes:
Properties are:
a) uint32_t aliased to 'unsigned long'
b) 'unsigned int' is at least 32-bit wide.
It seems unlikely that any implementation would make such a
choice. Can you name one that does?
Four out of four target for which I write C programs for living in
this decade:
- Altera Nios2 (nios2-elf-gcc)
- Arm Cortex-M bare metal (arm-none-eabi-gcc)
- Win32-i386, various compilers
- Win64-Amd64,various compilers
I find that surprising. I just tried a test program that prints
the name of the type uint32_t is an alias for (using _Generic),
and it's alias to unsigned int on every implementation I tried.
(Your properties are limited to systems with 32-bit int and long.)
For an implementation where int and long are both 32 bits, it
wouldn't have surprised me for uint32_t to be an alias either for
unsigned int or for unsigned long, and I wouldn't care either way
beyond idle curiosity, but all the implementations I've tried choose
to use unsigned int.
Well, if I would be pedantic, then in this decade I also wrote
several programs for Arm32 Linux, where I don't know whether
uint32_t is alias of 'unsigned int' or 'unsigned long', few
programs for AMD64 Linux, where I know that uint32_t is an alias of 'unsigned long' and may be one program for ARM64 Linux that is the
same as AMD64 Linux. But all those outliers together constitute a
tiny fraction of the code that I wrote recently.
One advantage of my approach is that I don't have to know or care
what the underlying type of uint32_t is.
On Mon, 12 Jan 2026 08:21:43 +0100
David Brown <david.brown@hesbynett.no> wrote:
On 11/01/2026 23:56, Keith Thompson wrote:
Michael S <already5chosen@yahoo.com> writes:
The solution that I would prefer would be universal adaption of
Microsoft's size specifiers I32 and I64. They are not going to win
beauty competition, but in practice they are a lot more convenient
to use than standard macros and are equally good at carrying
programmer's intentions.
The relative beauty of a feature that isn't available hardly seems
relevant.
The ideal solution is to write correct, and preferably portable,
code in the first place. There are often good reasons to write
non-portable code, but I suggest that fiding the correct format
string to be ugly is not one of them.
(Microsoft's documentation says that "I32" prefix applies to an
argument of type __int32 or unsigned __int32. I don't know whether
__int32 is compatible with int, with long, or neither, and I don't
much care. I don't know what Microsoft guarantees about printf
with incompatible types that happen to have the same size.)
C23 includes length specifiers with explicit bit counts, so "%w32u"
is for an unsigned integer argument of 32 bits:
"""
wN Specifies that a following b, B, d, i, o, u, x, or X conversion
specifier applies to an integer argument with a specific width where
N is a positive decimal integer with no leading zeros (the argument
will have been promoted according to the integer promotions, but its
value shall be converted to the unpromoted type); or that a following
n conversion specifier applies to a pointer to an integer type
argument with a width of N bits. All minimum-width integer types
(7.22.1.2) and exact-width integer types (7.22.1.1) defined in the
header <stdint.h> shall be supported. Other supported values of N are
implementation-defined. """
That looks to me that it would be a correct specifier for uint32_t,
and should also be fully defined behaviour for unsigned int and
unsigned long if these are 32 bits wide.
It sounds very good.
Except that none of my four targets of major interest supports C23 at
the moment. Esp. so at the level of standard library.
For one of them (Nios2) in the absence of something VERY unexpected
there never be support (gcc stopped support for Nios2 2 or 3 years ago).
For the other three, it will take time. I can't even guess how long,
except that I know that support versions of arm-none-eabi-gcc lags two
years behind "hosted" x86-64 and ARM64 versions, so I can guess that it
would take significant time to catch up.
C23 includes length specifiers with explicit bit counts, so "%w32u" is
for an unsigned integer argument of 32 bits:
"""
wN Specifies that a following b, B, d, i, o, u, x, or X conversion
specifier applies to an integer argument with a specific width
where N is a positive decimal integer with no leading zeros
(the argument will have been promoted according to the integer
promotions, but its value shall be converted to the unpromoted
type); or that a following n conversion specifier applies to a
pointer to an integer type argument with a width of N bits. All
minimum-width integer types (7.22.1.2) and exact-width integer
types (7.22.1.1) defined in the header <stdint.h> shall be
supported. Other supported values of N are implementation-defined.
"""
That looks to me that it would be a correct specifier for uint32_t,
and should also be fully defined behaviour for unsigned int and
unsigned long if these are 32 bits wide.
David Brown <david.brown@hesbynett.no> writes:
[...]
C23 includes length specifiers with explicit bit counts, so "%w32u" is
for an unsigned integer argument of 32 bits:
"""
wN Specifies that a following b, B, d, i, o, u, x, or X conversion
specifier applies to an integer argument with a specific width
where N is a positive decimal integer with no leading zeros
(the argument will have been promoted according to the integer
promotions, but its value shall be converted to the unpromoted
type); or that a following n conversion specifier applies to a
pointer to an integer type argument with a width of N bits. All
minimum-width integer types (7.22.1.2) and exact-width integer
types (7.22.1.1) defined in the header <stdint.h> shall be
supported. Other supported values of N are implementation-defined.
"""
That looks to me that it would be a correct specifier for uint32_t,
Yes, so for example this:
uint32_t n = 42;
printf("n = %w32u\n", n);
is correct, if I'm reading it correctly. It's also correct for uint_least32_t, which is expected to be the same type as uint32_t
if the latter exists. There's also support for the [u]int_fastN_t
types, using for example "%wf32u" in place of "%w32u".
and should also be fully defined behaviour for unsigned int and
unsigned long if these are 32 bits wide.
No, I don't think C23 says that. If int and long happen to be the same width, they are still incompatible, and there is no printf format
specifier that has defined behavior for both.
That first sentence is a bit ambiguous
wN Specifies that a following b, B, d, i, o, u, x, or X conversion
specifier applies to an integer argument with a specific width ...
but I don't think it means that it must accept *any* integer type
of the specified width.
Later in the same paragraph, it says that all [u]intN_t and
[u]int_leastN_t types shall be supported -- all such *types*, not
all such *widths*. And it doesn't say that the predefined types
shall be supported.
Paragraph 9 says:
fprintf shall behave as if it uses va_arg with a type argument
naming the type resulting from applying the default argument
promotions to the type corresponding to the conversion specification
and then converting the result of the va_arg expansion to the type
corresponding to the conversion specification.
And in the description for the va_arg macro (whose second argument
is a type name):
If *type* is not compatible with the type of the actual
next argument (as promoted according to the default argument
promotions), the behavior is undefined, except for the following
cases: ...
Corresponding signed and unsigned types are supported if the value
is representable in both, but there's no provision for mixing int
and long even if they have the same width.
If printf is implemented using <stdarg.h>, what type name can it
pass to the va_arg() macro given a "%w32u" specification? It can
only pass uint32_t or uint_least32_t (or it can pass unsigned int
or unsigned long *if* that type is compatible with the uint32_t).
(And C23 adds a requirement that [u]int_leastN_t is the same type as [u]intN_t if the latter exists; perhaps this is why.)
Prior to C17, there is no conversion specification that's valid for
both int and long, even if they're the same width. Changing that
in C23 would have been a significant change, but there's no mention
of it, even in a footnote.
The "%w..." format specifiers are simpler (and IMHO less ugly)
than the macros in <inttypes.h>, but they don't add any fundamental
new capability.
Given the format specifier "%w32u", the corresponding argument must
be of type uint32_t, or it can be of type int32_t and representable
in both, or it can be of a type compatible with [u]int32_t. I expect
that in most or all implementations, the undefined behavior of
passing an incompatible type with the same width and representation
will appear as if it "worked", but a compile-time warning is likely
if the format is a string literal.
And of course if you want to print a uint32_t value, you can always
cast it to unsigned long and use "%u".
gcc has supported the format, along with much of C23, since gcc 13,
and ARM's gcc-based toolchain version 13.2 is from October 2023. (The current version is 15.2 from December 2025.) But I don't know about
library support - that is a very different matter. (Compiler support
for printf really just means checking the format specifiers match the parameters.)
David Brown <david.brown@hesbynett.no> writes:
[...]
Context: %wN and %wfN printf length modifier, new in C23.
gcc has supported the format, along with much of C23, since gcc 13,
and ARM's gcc-based toolchain version 13.2 is from October 2023. (The
current version is 15.2 from December 2025.) But I don't know about
library support - that is a very different matter. (Compiler support
for printf really just means checking the format specifiers match the
parameters.)
Of course printf is implemented in the library, not in the compiler.
gcc has had format checking for %wN and %wfN since release 13, but
that's useless in the absence of library support.
Support in glibc was added 2023-06-19 and released in version 2.39.
Other C library implementations may or may not support it.
On 13/01/2026 01:06, Keith Thompson wrote:
David Brown <david.brown@hesbynett.no> writes:
[...]
Context: %wN and %wfN printf length modifier, new in C23.
gcc has supported the format, along with much of C23, since gcc 13,Of course printf is implemented in the library, not in the compiler.
and ARM's gcc-based toolchain version 13.2 is from October 2023. (The
current version is 15.2 from December 2025.) But I don't know about
library support - that is a very different matter. (Compiler support
for printf really just means checking the format specifiers match the
parameters.)
Primarily, yes. But like all standard library functions, compilers
can have special handling in some ways. This is more obvious for
functions like memcpy, where the compiler can often generate
significantly better code (specially for small known sizes). As far
as I know, the only optimisation gcc does on printf is turn something
like printf("Hello\n") into puts("Hello"). Hypothetically, there is
nothing to stop a compiler being a great deal more sophisticated than
that, and doing the format-string interpretation directly in some way.
gcc has had format checking for %wN and %wfN since release 13, but
that's useless in the absence of library support.
Yes.
Support in glibc was added 2023-06-19 and released in version 2.39.
Other C library implementations may or may not support it.
glibc is not particularly relevant for non-Linux embedded
systems. newlib (and newlib-nano) are a common choice for such
systems, but I have no idea if it currently has support for those
formats.
David Brown <david.brown@hesbynett.no> writes:
On 13/01/2026 01:06, Keith Thompson wrote:
David Brown <david.brown@hesbynett.no> writes:
[...]
Context: %wN and %wfN printf length modifier, new in C23.
gcc has supported the format, along with much of C23, since gcc 13,Of course printf is implemented in the library, not in the compiler.
and ARM's gcc-based toolchain version 13.2 is from October 2023. (The >>>> current version is 15.2 from December 2025.) But I don't know about
library support - that is a very different matter. (Compiler support
for printf really just means checking the format specifiers match the
parameters.)
Primarily, yes. But like all standard library functions, compilers
can have special handling in some ways. This is more obvious for
functions like memcpy, where the compiler can often generate
significantly better code (specially for small known sizes). As far
as I know, the only optimisation gcc does on printf is turn something
like printf("Hello\n") into puts("Hello"). Hypothetically, there is
nothing to stop a compiler being a great deal more sophisticated than
that, and doing the format-string interpretation directly in some way.
Sure, but in practice the library support for printf is the only thing
that matters. If your library doesn't support %wN, having the compiler recognize it doesn't help. I'm not sure why you even mentioned gcc in
this context.
gcc has had format checking for %wN and %wfN since release 13, but
that's useless in the absence of library support.
Yes.
Support in glibc was added 2023-06-19 and released in version 2.39.
Other C library implementations may or may not support it.
glibc is not particularly relevant for non-Linux embedded
systems. newlib (and newlib-nano) are a common choice for such
systems, but I have no idea if it currently has support for those
formats.
Of course, that's why I mentioned other C library implementations.
glibc happens to be the one about which I had some relevant
information.
The versions of musl and dietlibc that I have on my Ubuntu system
don't support %wN. The version of newlib I have on Cygwin also
doesn't support it. PellesC on Windows does.
On 13/01/2026 09:53, Keith Thompson wrote:
David Brown <david.brown@hesbynett.no> writes:
On 13/01/2026 01:06, Keith Thompson wrote:
David Brown <david.brown@hesbynett.no> writes:
[...]
Context: %wN and %wfN printf length modifier, new in C23.
gcc has supported the format, along with much of C23, since gccOf course printf is implemented in the library, not in the
13, and ARM's gcc-based toolchain version 13.2 is from October
2023. (The current version is 15.2 from December 2025.) But I
don't know about library support - that is a very different
matter. (Compiler support for printf really just means checking
the format specifiers match the parameters.)
compiler.
Primarily, yes. But like all standard library functions, compilers
can have special handling in some ways. This is more obvious for
functions like memcpy, where the compiler can often generate
significantly better code (specially for small known sizes). As
far as I know, the only optimisation gcc does on printf is turn
something like printf("Hello\n") into puts("Hello").
Hypothetically, there is nothing to stop a compiler being a great
deal more sophisticated than that, and doing the format-string
interpretation directly in some way.
Sure, but in practice the library support for printf is the only
thing that matters. If your library doesn't support %wN, having
the compiler recognize it doesn't help. I'm not sure why you even mentioned gcc in this context.
I had several reasons (I would want compiler checking of the format
before using it, I know the state of support in gcc, and I had
mentioned ARM's gcc toolchains as they are the standard choice of
toolchains for embedded ARM systems).
On Tue, 13 Jan 2026 11:09:55 +0100
David Brown <david.brown@hesbynett.no> wrote:
On 13/01/2026 09:53, Keith Thompson wrote:
David Brown <david.brown@hesbynett.no> writes:
On 13/01/2026 01:06, Keith Thompson wrote:
David Brown <david.brown@hesbynett.no> writes:
[...]
Context: %wN and %wfN printf length modifier, new in C23.
gcc has supported the format, along with much of C23, since gccOf course printf is implemented in the library, not in the
13, and ARM's gcc-based toolchain version 13.2 is from October
2023. (The current version is 15.2 from December 2025.) But I
don't know about library support - that is a very different
matter. (Compiler support for printf really just means checking
the format specifiers match the parameters.)
compiler.
Primarily, yes. But like all standard library functions, compilers
can have special handling in some ways. This is more obvious for
functions like memcpy, where the compiler can often generate
significantly better code (specially for small known sizes). As
far as I know, the only optimisation gcc does on printf is turn
something like printf("Hello\n") into puts("Hello").
Hypothetically, there is nothing to stop a compiler being a great
deal more sophisticated than that, and doing the format-string
interpretation directly in some way.
Sure, but in practice the library support for printf is the only
thing that matters. If your library doesn't support %wN, having
the compiler recognize it doesn't help. I'm not sure why you even
mentioned gcc in this context.
I had several reasons (I would want compiler checking of the format
before using it, I know the state of support in gcc, and I had
mentioned ARM's gcc toolchains as they are the standard choice of
toolchains for embedded ARM systems).
[O.T.]
AFAIK, that's no longer the case. For last 3-4 years Cortex-M MCU
vendors, in particular STMicro and TI, intensely push clang as a default compiler in their free IDEs.
Today, in order to use gcc in the new embedded ARM project, developer
has to make a conscious effort of rejecting vendor's default. Most
likely, gcc compiler does not come as part of default installation
package. It has to be downloaded and installed separately.
I would guess that overwhelming majority of devs does not bother.
On 13/01/2026 12:45, Michael S wrote:
On Tue, 13 Jan 2026 11:09:55 +0100
David Brown <david.brown@hesbynett.no> wrote:
On 13/01/2026 09:53, Keith Thompson wrote:
David Brown <david.brown@hesbynett.no> writes:
On 13/01/2026 01:06, Keith Thompson wrote:
David Brown <david.brown@hesbynett.no> writes:
[...]
Context: %wN and %wfN printf length modifier, new in C23.
gcc has supported the format, along with much of C23, since gccOf course printf is implemented in the library, not in the
13, and ARM's gcc-based toolchain version 13.2 is from October
2023. (The current version is 15.2 from December 2025.) But I
don't know about library support - that is a very different
matter. (Compiler support for printf really just means
checking the format specifiers match the parameters.)
compiler.
Primarily, yes. But like all standard library functions,
compilers can have special handling in some ways. This is more
obvious for functions like memcpy, where the compiler can often
generate significantly better code (specially for small known
sizes). As far as I know, the only optimisation gcc does on
printf is turn something like printf("Hello\n") into
puts("Hello"). Hypothetically, there is nothing to stop a
compiler being a great deal more sophisticated than that, and
doing the format-string interpretation directly in some way.
Sure, but in practice the library support for printf is the only
thing that matters. If your library doesn't support %wN, having
the compiler recognize it doesn't help. I'm not sure why you even
mentioned gcc in this context.
I had several reasons (I would want compiler checking of the format
before using it, I know the state of support in gcc, and I had
mentioned ARM's gcc toolchains as they are the standard choice of
toolchains for embedded ARM systems).
[O.T.]
AFAIK, that's no longer the case. For last 3-4 years Cortex-M MCU
vendors, in particular STMicro and TI, intensely push clang as a
default compiler in their free IDEs.
As far as I know, ST uses ARM's gcc build as their normal toolchain.
I haven't looked at TI's development tools for a good while.
But it is certainly the case that clang-based toolchains are gaining
in popularity for non-Linux embedded ARM. I've been looking at them
myself, and see advantages and disadvantages. However, I believe gcc
is still dominated by a large margin, and ARM makes regular releases
of complete toolchain packages (compiler, libraries, debugger, etc.).
Today, in order to use gcc in the new embedded ARM project,
developer has to make a conscious effort of rejecting vendor's
default. Most likely, gcc compiler does not come as part of default installation package. It has to be downloaded and installed
separately. I would guess that overwhelming majority of devs does
not bother.
My experience is that the majority of microcontroller vendors provide
gcc toolchains as part of their default installations. They used to
have their own gcc toolchain builds, or use third-parties like Code Sourcery, but these days they usually use an off-the-shelf ARM
package. But several vendors are in a transition period where they
are gradually moving from established Eclipse-based tools towards VS
Code tools, and perhaps clang is either an option or default with
their newer IDEs. Those vendors provide, support and maintain both
IDEs at the moment, but sometimes detailed support for particular microcontrollers only exists for one of them.
I agree that most developers will use whatever compiler comes as
default with their vendor-supplied IDEs. Personally, I think that's
fine for getting started, or for quick throw-away projects. For
anything serious I prefer to have the build controlled from outside
the IDE (by a makefile), using the toolchain I choose (typically the
latest ARM gcc toolchain when the project starts, then remaining
consistent throughout the lifetime of the project). The IDEs can
still be good editors, IDEs, debuggers, etc.
On Sun, 11 Jan 2026 04:59:47 -0800
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
Michael S <already5chosen@yahoo.com> writes:
On Sat, 10 Jan 2026 22:02:03 -0500
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
On 2026-01-09 07:18, Michael S wrote:
On Thu, 8 Jan 2026 19:31:13 -0500...
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
wrote:
I'd have no problem with your approach if you hadn't falsely
claimed that "It is correct on all platforms".
Which I didn't.
On 2026-01-07 19:38, Michael S wrote:
...
> No, it is correct on all implementation.
The quote is taken out of context.
The context was that on platforms that have properties (a) and (b)
(see below) printing variables declared as uint32_t via %u is
probably UB according to the Standard (I don't know for sure,
however it is probable),
I'm sure. uint32_t is an alias for some predefined integer type.
This:
uint32_t n = 42;
printf("%u\n", n);
has undefined behavior *unless* uint32_t happens to an alias for
unsigned int in the current implementation -- not just any 32-bit
unsigned integer type, only unsigned int.
If uint32_t is an alias for unsigned long (which implies that
unsigned long is exactly 32 bits), then the call's behavior is
undefined. (It might happen to "work".)
What exactly, assuming that conditions (a) and (b) fulfilled, should implementation do to prevent it from working?
I mean short of completely crazy things that will make maintainer
immediately fired?
If uint32_t and unsigned long have different sizes, it still might
happen happen to "work", depending on calling conventions. Passing a
32-bit argument and telling printf to expect a 64-bit value clearly
has undefined behavior, but perhaps both happen to be passed in 64-bit
registers, for example.
And that is sort of intimate knowledge of the ABI that I don't want to exploit, as already mentioned in my other post in this sub-thread.
On Sat, 10 Jan 2026 22:02:03 -0500
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
On 2026-01-09 07:18, Michael S wrote:
On Thu, 8 Jan 2026 19:31:13 -0500...
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
I'd have no problem with your approach if you hadn't falsely
claimed that "It is correct on all platforms".
Which I didn't.
On 2026-01-07 19:38, Michael S wrote:
...
No, it is correct on all implementation.
The quote is taken out of context.
The context was that on platforms that have properties (a) and (b) (see below) printing variables declared as uint32_t via %u is probably UB according to the Standard (I don't know for sure, however it is
probable), but it can't cause troubles with production C compiler. Or
with any C compiler that is made in intention of being used rather than crafted to prove theoretical points.
Properties are:
a) uint32_t aliased to 'unsigned long'
b) 'unsigned int' is at least 32-bit wide.
I never claimed that it is good idea on targets with 'unsigned int'
that is narrower.
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.
On 2026-01-11 08:32, Michael S wrote:
On Sun, 11 Jan 2026 04:59:47 -0800
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
Michael S <already5chosen@yahoo.com> writes:
On Sat, 10 Jan 2026 22:02:03 -0500
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
On 2026-01-09 07:18, Michael S wrote:
On Thu, 8 Jan 2026 19:31:13 -0500...
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
wrote:
I'd have no problem with your approach if you hadn't falsely
claimed that "It is correct on all platforms".
Which I didn't.
On 2026-01-07 19:38, Michael S wrote:
...
> No, it is correct on all implementation.
The quote is taken out of context.
The context was that on platforms that have properties (a) and (b)
(see below) printing variables declared as uint32_t via %u is
probably UB according to the Standard (I don't know for sure,
however it is probable),
I'm sure. uint32_t is an alias for some predefined integer type.
This:
uint32_t n = 42;
printf("%u\n", n);
has undefined behavior *unless* uint32_t happens to an alias for
unsigned int in the current implementation -- not just any 32-bit
unsigned integer type, only unsigned int.
If uint32_t is an alias for unsigned long (which implies that
unsigned long is exactly 32 bits), then the call's behavior is
undefined. (It might happen to "work".)
What exactly, assuming that conditions (a) and (b) fulfilled, should
implementation do to prevent it from working?
I mean short of completely crazy things that will make maintainer
immediately fired?
I'm quite positive that you would consider anything that might give unexpected behavior to such code to be "crazy". The simplest example I
can think of is that unsigned int is big-endian, while unsigned long is little-endian, and I would even agree that such an implementation would
be peculiar, but such an implementation could be fully conforming to the
C standard.
If uint32_t and unsigned long have different sizes, it still might
happen happen to "work", depending on calling conventions. Passing a
32-bit argument and telling printf to expect a 64-bit value clearly
has undefined behavior, but perhaps both happen to be passed in 64-bit
registers, for example.
And that is sort of intimate knowledge of the ABI that I don't want to
exploit, as already mentioned in my other post in this sub-thread.
Which is precisely what's wrong about your approach - it relies upon intimate knowledge of the ABI. Specifically, it relies on unsigned int
and unsigned long happening to have exactly the same size and representation.
On 2026-01-11 08:32, Michael S wrote:
On Sun, 11 Jan 2026 04:59:47 -0800
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
Michael S <already5chosen@yahoo.com> writes:
On Sat, 10 Jan 2026 22:02:03 -0500
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
wrote:
On 2026-01-09 07:18, Michael S wrote:
On Thu, 8 Jan 2026 19:31:13 -0500...
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
wrote:
I'd have no problem with your approach if you hadn't falsely
claimed that "It is correct on all platforms".
Which I didn't.
On 2026-01-07 19:38, Michael S wrote:
...
> No, it is correct on all implementation.
The quote is taken out of context.
The context was that on platforms that have properties (a) and (b)
(see below) printing variables declared as uint32_t via %u is
probably UB according to the Standard (I don't know for sure,
however it is probable),
I'm sure. uint32_t is an alias for some predefined integer type.
This:
uint32_t n = 42;
printf("%u\n", n);
has undefined behavior *unless* uint32_t happens to an alias for
unsigned int in the current implementation -- not just any 32-bit
unsigned integer type, only unsigned int.
If uint32_t is an alias for unsigned long (which implies that
unsigned long is exactly 32 bits), then the call's behavior is
undefined. (It might happen to "work".)
What exactly, assuming that conditions (a) and (b) fulfilled, should implementation do to prevent it from working?
I mean short of completely crazy things that will make maintainer immediately fired?
I'm quite positive that you would consider anything that might give unexpected behavior to such code to be "crazy". The simplest example
I can think of is that unsigned int is big-endian, while unsigned
long is little-endian, and I would even agree that such an
implementation would be peculiar, but such an implementation could be
fully conforming to the C standard.
If uint32_t and unsigned long have different sizes, it still might
happen happen to "work", depending on calling conventions.
Passing a 32-bit argument and telling printf to expect a 64-bit
value clearly has undefined behavior, but perhaps both happen to
be passed in 64-bit registers, for example.
And that is sort of intimate knowledge of the ABI that I don't want
to exploit, as already mentioned in my other post in this
sub-thread.
Which is precisely what's wrong about your approach - it relies upon intimate knowledge of the ABI. Specifically, it relies on unsigned
int and unsigned long happening to have exactly the same size and representation.
On 2026-01-11 06:20, Michael S wrote:
On Sat, 10 Jan 2026 22:02:03 -0500
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
On 2026-01-09 07:18, Michael S wrote:
On Thu, 8 Jan 2026 19:31:13 -0500...
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
wrote:
I'd have no problem with your approach if you hadn't falsely
claimed that "It is correct on all platforms".
Which I didn't.
On 2026-01-07 19:38, Michael S wrote:
...
No, it is correct on all implementation.
The quote is taken out of context.
The context was that on platforms that have properties (a) and (b)
(see below) printing variables declared as uint32_t via %u is
probably UB according to the Standard (I don't know for sure,
however it is probable), but it can't cause troubles with
production C compiler. Or with any C compiler that is made in
intention of being used rather than crafted to prove theoretical
points. Properties are:
a) uint32_t aliased to 'unsigned long'
b) 'unsigned int' is at least 32-bit wide.
I never claimed that it is good idea on targets with 'unsigned int'
that is narrower.
I've looked for a previous restriction of this discussion to cases
covered by a) and b) above. The closest I could find is the following:
In the case I am talking about n declared as uint32_t.
uint32_t is an alias of 'unsigned long' on 32-bit embedded targets,
on 32-bit Linux, on 32-bit Windows and on 64-bit Windows. It is
alias of 'unsigned int' on 64-bit Linux.
Note several points: that is a period after the first use of
"uint32_t", so "the case" you're specifying ends there. I read the
next three lines as information about your working environment, not restrictions on the claimed validity of your preference for "%u" over
"%lu". There is no mention of a restriction on the size of "unsigned
int".
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.)
On Tue, 13 Jan 2026 21:24:16 -0500...
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
I'm quite positive that you would consider anything that might give
unexpected behavior to such code to be "crazy". The simplest example
I can think of is that unsigned int is big-endian, while unsigned
long is little-endian, and I would even agree that such an
implementation would be peculiar, but such an implementation could be
fully conforming to the C standard.
You are inventive!
I've spent most of my career working under rules that explicitlyWhich 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.
David Brown <david.brown@hesbynett.no> writes:
[...]
Would it be allowed (in the sense of being possible in a hypothetical
but fully conforming implementation) to have "unsigned long" be
32-bit, without padding, while "unsigned int" is 64-bit wide with 32
value bits and 32 padding bits? A cpu might be able to handle 64-bit
lumps faster than 32-bit lumps and choose such a setup to make
"unsigned int" as fast as it can. (uint32_t in this case would be an
alias for "unsigned long", as it can't have padding bits.)
The *width* of an integer type is the number of value bits plus the sign
bit, if any, so "64-bit wide" is an incorrect description.
What would be possible is:
- CHAR_BIT * sizeof (unsigned int) == 64
- UINT_WIDTH == 32 (32 padding bits)
- CHAR_BIT * sizeof (unsigned long) == 32
- ULONG_WIDTH == 32 (no padding bits)
The *_WIDTH macros are new in C23.
[...]
On 2026-01-14 04:03, Michael S wrote:
On Tue, 13 Jan 2026 21:24:16 -0500...
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
I'm quite positive that you would consider anything that might give
unexpected behavior to such code to be "crazy". The simplest
example I can think of is that unsigned int is big-endian, while
unsigned long is little-endian, and I would even agree that such an
implementation would be peculiar, but such an implementation could
be fully conforming to the C standard.
You are inventive!
As a programmer, I paid close attention to what was and was not
guaranteed about the software that I used. As a result, I've noticed
many things, such as the fact that the standard imposes no
requirements on the order of the bytes (or even of the bits) that are
used to represent arithmetic values.
...
Which is precisely what's wrong about your approach - it relies
upon intimate knowledge of the ABI. Specifically, it relies on
unsigned int and unsigned long happening to have exactly the same
size and representation.
I consider the latter a basic knowledge of ABI rather than anI've spent most of my career working under rules that explicitly
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.
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.
On Tue, 13 Jan 2026 22:17:09 -0500
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
On 2026-01-11 06:20, Michael S wrote:
On Sat, 10 Jan 2026 22:02:03 -0500
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
On 2026-01-09 07:18, Michael S wrote:
On Thu, 8 Jan 2026 19:31:13 -0500...
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
wrote:
I'd have no problem with your approach if you hadn't falsely
claimed that "It is correct on all platforms".
Which I didn't.
On 2026-01-07 19:38, Michael S wrote:
...
No, it is correct on all implementation.
The quote is taken out of context.
The context was that on platforms that have properties (a) and (b)
(see below) printing variables declared as uint32_t via %u is
probably UB according to the Standard (I don't know for sure,
however it is probable), but it can't cause troubles with
production C compiler. Or with any C compiler that is made in
intention of being used rather than crafted to prove theoretical
points. Properties are:
a) uint32_t aliased to 'unsigned long'
b) 'unsigned int' is at least 32-bit wide.
I never claimed that it is good idea on targets with 'unsigned int'
that is narrower.
I've looked for a previous restriction of this discussion to cases
covered by a) and b) above. The closest I could find is the following:
In the case I am talking about n declared as uint32_t.
uint32_t is an alias of 'unsigned long' on 32-bit embedded targets,
on 32-bit Linux, on 32-bit Windows and on 64-bit Windows. It is
alias of 'unsigned int' on 64-bit Linux.
Note several points: that is a period after the first use of
"uint32_t", so "the case" you're specifying ends there. I read the
next three lines as information about your working environment, not
restrictions on the claimed validity of your preference for "%u" over
"%lu". There is no mention of a restriction on the size of "unsigned
int".
Ignoring for a minute that what I claimed about 32-bit Linux is
at best non-universal and at worst universally wrong, how would you
formulate what I meant?
My knowledge of English punctuation rules is rather minimal and even
less than that for its US American variant.
I'm not sure exactly what you intended. And, as I mentioned in another sub-thread, I've worked for most of my career under rules that[...]
prohibited me from writing code that depends upon the kinds of details
that you're talking about - as a result, I've had little reason to familiarize myself with those details. However, I can say that using
"%u" to print a value of unsigned long type has no chance of working
unless unsigned int and unsigned long have the same size and
representation. Even if they do, the behavior is still undefined, but
there's a pretty good chance it will work.
James Kuyper <jameskuyper@alumni.caltech.edu> writes:
[...]
I'm not sure exactly what you intended. And, as I mentioned in another[...]
sub-thread, I've worked for most of my career under rules that
prohibited me from writing code that depends upon the kinds of details
that you're talking about - as a result, I've had little reason to
familiarize myself with those details. However, I can say that using
"%u" to print a value of unsigned long type has no chance of working
unless unsigned int and unsigned long have the same size and
representation. Even if they do, the behavior is still undefined, but
there's a pretty good chance it will work.
On one implementation (gcc, glibc, 64 bits), it *can* "work":
```
#include <stdio.h>
int main(void) {
unsigned long x = 123456789;
printf("sizeof (unsigned) = %zu\n", sizeof (unsigned));
printf("sizeof (unsigned long) = %zu\n", sizeof (unsigned long));
printf("x = %u\n", x);
}
```
The output on my system (after some compiler warnings):
```
sizeof (unsigned) = 4
sizeof (unsigned long) = 8
x = 123456789
```
Apparently printf tries to grab a 32-bit value and happens to get
the low-order 32 bits of the 64-bit value that was passed. A value
exceeding LONG_MAX is not printed correctly, but in principle it
could be.
On 2026-01-15 07:00, Keith Thompson wrote:
James Kuyper <jameskuyper@alumni.caltech.edu> writes:
[...]
I'm not sure exactly what you intended. And, as I mentioned in another[...]
sub-thread, I've worked for most of my career under rules that
prohibited me from writing code that depends upon the kinds of details
that you're talking about - as a result, I've had little reason to
familiarize myself with those details. However, I can say that using
"%u" to print a value of unsigned long type has no chance of working
unless unsigned int and unsigned long have the same size and
representation. Even if they do, the behavior is still undefined, but
there's a pretty good chance it will work.
On one implementation (gcc, glibc, 64 bits), it *can* "work":
```
#include <stdio.h>
int main(void) {
unsigned long x = 123456789;
printf("sizeof (unsigned) = %zu\n", sizeof (unsigned));
printf("sizeof (unsigned long) = %zu\n", sizeof (unsigned long));
printf("x = %u\n", x);
}
```
The output on my system (after some compiler warnings):
```
sizeof (unsigned) = 4
sizeof (unsigned long) = 8
x = 123456789
```
Apparently printf tries to grab a 32-bit value and happens to get
the low-order 32 bits of the 64-bit value that was passed. A value
exceeding LONG_MAX is not printed correctly, but in principle it
could be.
I knew about that possibility, and had intended to word my comment to
cover it, but I forgot. Thanks for covering it. The key point is that
this only works for a large but limited range of values - it cannot work
in general.
Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
scott@slp53.sl.home (Scott Lurndal) writes:
Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
I was responding to Scotty Lurndal's statement that the C
standard was being paraphrased (by someone, it didn't matter to
me who). I don't care about whether his statement is true; my
interest is only in what part of the C standard he thinks is
being paraphrased. He is in a position to answer that question,
and more to the point he is the only person who is.
It's pretty clear that the standard describes the printf
function and the methods used to match the format characters
to the data types of the arguments. The fact that James
framed that as advice doesn't change interpretation of
the text of the standard, whether or not you consider
that to be a paraphrase.
"The main rules for paraphrasing are to fully understand the
original text, restate its core idea in your own words and
sentence structure, use synonyms, and always cite the original
source to avoid plagiarism, even if the wording is different.
I see where the C standard says the macros in inttypes.h are
suitable for use with printf (and scanf). That isn't at all
the same as saying people should use them.
Why on earth would the put them there if they didn't expect
them to be used?
On Sun, 11 Jan 2026 11:51:43 -0800
Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
Michael S <already5chosen@yahoo.com> writes:
On Sat, 10 Jan 2026 22:02:03 -0500
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
On 2026-01-09 07:18, Michael S wrote:
On Thu, 8 Jan 2026 19:31:13 -0500
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
wrote:
...
I'd have no problem with your approach if you hadn't falsely
claimed that "It is correct on all platforms".
Which I didn't.
On 2026-01-07 19:38, Michael S wrote:
...
No, it is correct on all implementation.
The quote is taken out of context.
The context was that on platforms that have properties (a) and (b)
(see below) printing variables declared as uint32_t via %u is
probably UB according to the Standard (I don't know for sure,
however it is probable), but it can't cause troubles with
production C compiler. Or with any C compiler that is made in
intention of being used rather than crafted to prove theoretical
points. Properties are:
a) uint32_t aliased to 'unsigned long'
b) 'unsigned int' is at least 32-bit wide.
It seems unlikely that any implementation would make such a
choice. Can you name one that does?
Four out of four target for which I write C programs for living in this decade:
- Altera Nios2 (nios2-elf-gcc)
- Arm Cortex-M bare metal (arm-none-eabi-gcc)
- Win32-i386, various compilers
- Win64-Amd64,various compilers
Well, if I would be pedantic, then in this decade I also wrote several programs for Arm32 Linux, where I don't know whether uint32_t is alias
of 'unsigned int' or 'unsigned long', few programs for AMD64 Linux,
where I know that uint32_t is an alias of 'unsigned long' and may be one program for ARM64 Linux that is the same as AMD64 Linux.
But all those outliers together constitute a tiny fraction of the code
that I wrote recently.
Michael S <already5chosen@yahoo.com> writes:
On Sun, 11 Jan 2026 11:51:43 -0800
Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
Michael S <already5chosen@yahoo.com> writes:
On Sat, 10 Jan 2026 22:02:03 -0500
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu> wrote:
On 2026-01-09 07:18, Michael S wrote:
On Thu, 8 Jan 2026 19:31:13 -0500
"James Russell Kuyper Jr." <jameskuyper@alumni.caltech.edu>
wrote:
...
I'd have no problem with your approach if you hadn't falsely
claimed that "It is correct on all platforms".
Which I didn't.
On 2026-01-07 19:38, Michael S wrote:
...
No, it is correct on all implementation.
The quote is taken out of context.
The context was that on platforms that have properties (a) and (b)
(see below) printing variables declared as uint32_t via %u is
probably UB according to the Standard (I don't know for sure,
however it is probable), but it can't cause troubles with
production C compiler. Or with any C compiler that is made in
intention of being used rather than crafted to prove theoretical
points. Properties are:
a) uint32_t aliased to 'unsigned long'
b) 'unsigned int' is at least 32-bit wide.
It seems unlikely that any implementation would make such a
choice. Can you name one that does?
Four out of four target for which I write C programs for living in this
decade:
- Altera Nios2 (nios2-elf-gcc)
- Arm Cortex-M bare metal (arm-none-eabi-gcc)
- Win32-i386, various compilers
- Win64-Amd64,various compilers
Interesting. I wonder what factors motivated such a choice.
Well, if I would be pedantic, then in this decade I also wrote several
programs for Arm32 Linux, where I don't know whether uint32_t is alias
of 'unsigned int' or 'unsigned long', few programs for AMD64 Linux,
where I know that uint32_t is an alias of 'unsigned long' and may be one
program for ARM64 Linux that is the same as AMD64 Linux.
But all those outliers together constitute a tiny fraction of the code
that I wrote recently.
If variable 'u' is declared as uint32_t, a way to print it that is
easy and also type-safe is
printf( " u is %lu\n", u+0LU );
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 );
Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
[...]
If variable 'u' is declared as uint32_t, a way to print it that is
easy and also type-safe is
printf( " u is %lu\n", u+0LU );
I prefer
printf("u is %lu\n", (unsigned)long_u);
I find it clearer.
On 03/02/2026 22:43, Keith Thompson wrote:
Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
[...]
If variable 'u' is declared as uint32_t, a way to print it that is
easy and also type-safe is
printf( " u is %lu\n", u+0LU );
I prefer
printf("u is %lu\n", (unsigned)long_u);
I find it clearer.
Is there a typo in there, or is the variable actually called 'long_u'?
Then the message doesn't match.
David Brown <david.brown@hesbynett.no> writes:
[...]
C23 includes length specifiers with explicit bit counts, so "%w32u" is
for an unsigned integer argument of 32 bits:
"""
wN Specifies that a following b, B, d, i, o, u, x, or X conversion
specifier applies to an integer argument with a specific width
where N is a positive decimal integer with no leading zeros
(the argument will have been promoted according to the integer
promotions, but its value shall be converted to the unpromoted
type); or that a following n conversion specifier applies to a
pointer to an integer type argument with a width of N bits. All
minimum-width integer types (7.22.1.2) and exact-width integer
types (7.22.1.1) defined in the header <stdint.h> shall be
supported. Other supported values of N are implementation-defined.
"""
That looks to me that it would be a correct specifier for uint32_t,
Yes, so for example this:
uint32_t n = 42;
printf("n = %w32u\n", n);
is correct, if I'm reading it correctly. It's also correct for uint_least32_t, which is expected to be the same type as uint32_t
if the latter exists. There's also support for the [u]int_fastN_t
types, using for example "%wf32u" in place of "%w32u".
and should also be fully defined behaviour for unsigned int and
unsigned long if these are 32 bits wide.
No, I don't think C23 says that.
If int and long happen to be the same
width, they are still incompatible, and there is no printf format
specifier that has defined behavior for both.
That first sentence is a bit ambiguous
wN Specifies that a following b, B, d, i, o, u, x, or X conversion
specifier applies to an integer argument with a specific width ...
but I don't think it means that it must accept *any* integer type
of the specified width.
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
David Brown <david.brown@hesbynett.no> writes:
[...]
C23 includes length specifiers with explicit bit counts, so "%w32u" is
for an unsigned integer argument of 32 bits:
"""
wN Specifies that a following b, B, d, i, o, u, x, or X conversion
specifier applies to an integer argument with a specific width
where N is a positive decimal integer with no leading zeros
(the argument will have been promoted according to the integer
promotions, but its value shall be converted to the unpromoted
type); or that a following n conversion specifier applies to a
pointer to an integer type argument with a width of N bits. All
minimum-width integer types (7.22.1.2) and exact-width integer
types (7.22.1.1) defined in the header <stdint.h> shall be
supported. Other supported values of N are implementation-defined.
"""
That looks to me that it would be a correct specifier for uint32_t,
Yes, so for example this:
uint32_t n = 42;
printf("n = %w32u\n", n);
is correct, if I'm reading it correctly. It's also correct for
uint_least32_t, which is expected to be the same type as uint32_t
if the latter exists. There's also support for the [u]int_fastN_t
types, using for example "%wf32u" in place of "%w32u".
and should also be fully defined behaviour for unsigned int and
unsigned long if these are 32 bits wide.
No, I don't think C23 says that.
Right, it doesn't.
If int and long happen to be the same
width, they are still incompatible, and there is no printf format
specifier that has defined behavior for both.
That first sentence is a bit ambiguous
wN Specifies that a following b, B, d, i, o, u, x, or X conversion
specifier applies to an integer argument with a specific width ...
but I don't think it means that it must accept *any* integer type
of the specified width.
As I read the standard there is no ambiguity. The first sentence
says what the length modifier means. The second sentence says
which types (if any) correspond to the description in the first
sentence.
Michael S <already5chosen@yahoo.com> writes:
On Tue, 06 Jan 2026 16:29:04 -0800
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
Michael S <already5chosen@yahoo.com> writes:
On Tue, 6 Jan 2026 10:31:41 -0500
James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
On 2026-01-06 04:29, Michael S wrote:
On Tue, 6 Jan 2026 00:27:04 -0000 (UTC)
Lawrence D?Oliveiro <ldo@nz.invalid> wrote:
...
Section 7.8 of the C spec defines macros you can use so you
don?t have to hard-code assumptions about the lengths of
integers in printf-format strings.
Did you ever try to use them? They look ugly.
Which is more important, correctness or beauty?
It depends.
When I know for sure that incorrectness has no consequences, like
in case of using %u to print 'unsigned long' on target with 32-bit
longs, or like using %llu to print 'unsigned long' on target with
64-bit longs, then beauty wins. Easily.
Seriously?
An example:
unsigned long n = 42;
printf("%u\n", n); // incorrect
printf("%lu\n", n); // correct
Are you really saying that the second version is so much uglier
than the first that you'd rather write incorrect code?
No, I don't think that it is much uglier. At worst, I think that
correct version is tiny bit uglier. Not enough for beauty to win
over "correctness", even when correctness is non-consequential.
I hoped that you followed the sub-thread from the beginning and
did not lost the context yet.
The context to which I replied was you favoring beauty over
correctness and using "%u" to print an unsigned long value as
an example.
I find it difficult to express how strongly I disagree.
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?
Bart <bc@freeuk.com> writes:
On 03/02/2026 15:47, Tim Rentsch wrote:[...]
If variable 'u' is declared as uint32_t, a way to print it that is
easy and also type-safe is
printf( " u is %lu\n", u+0LU );
What about a compound expression of several variables of mixed
integer types, possibly even mixed with floats, some of whose types
might either be conditional (depending on some macro), or opaque?
What is an example of a conditional/macro-dependent type?
Also what sort of opaque types do you have in mind?
What is the problem you want to solve here?
On 04/02/2026 14:22, Tim Rentsch wrote:
Bart <bc@freeuk.com> writes:
On 03/02/2026 15:47, Tim Rentsch wrote:[...]
If variable 'u' is declared as uint32_t, a way to print it that is
easy and also type-safe is
printf( " u is %lu\n", u+0LU );
What about a compound expression of several variables of mixed
integer types, possibly even mixed with floats, some of whose types
might either be conditional (depending on some macro), or opaque?
What is an example of a conditional/macro-dependent type?
Example from SDL2:
#if defined(_MSC_VER) && (_MSC_VER < 1600)
...
#ifndef _UINTPTR_T_DEFINED
#ifdef _WIN64
typedef unsigned __int64 uintptr_t;
#else
typedef unsigned int uintptr_t;
...
Example from SQLITE3:
#ifdef SQLITE_OMIT_FLOATING_POINT
# define double sqlite3_int64
#endif
Also what sort of opaque types do you have in mind?
Things like time_t and clock_t, or the equivalent from libraries.
Yes you could hunt down the exact underlying type (for clock_t in one
case, it was under 6 layers of typedefs and macros), but that would be
for a specific set of headers.
For system headers, somebody could be using a header with different definitions. For user-libraries, it might be a slightly different version.
What is the problem you want to solve here?
The problem is that C expects an exact format-code when trying to use *printf functions, and for that you need to know the exact types of the expressions being passed. For example:
uintptr_t x; // from above examples
double y; //
printf("x * y is %?", x * y); // What's '?'
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?
On 04/02/2026 17:44, Bart wrote:
On 04/02/2026 14:22, Tim Rentsch wrote:
Bart <bc@freeuk.com> writes:
On 03/02/2026 15:47, Tim Rentsch wrote:[...]
If variable 'u' is declared as uint32_t, a way to print it that is
easy and also type-safe is
printf( " u is %lu\n", u+0LU );
What about a compound expression of several variables of mixed
integer types, possibly even mixed with floats, some of whose types
might either be conditional (depending on some macro), or opaque?
What is an example of a conditional/macro-dependent type?
Example from SDL2:
#if defined(_MSC_VER) && (_MSC_VER < 1600)
...
#ifndef _UINTPTR_T_DEFINED
#ifdef _WIN64
typedef unsigned __int64 uintptr_t;
#else
typedef unsigned int uintptr_t;
...
Example from SQLITE3:
#ifdef SQLITE_OMIT_FLOATING_POINT
# define double sqlite3_int64
#endif
Also what sort of opaque types do you have in mind?
Things like time_t and clock_t, or the equivalent from libraries.
Yes you could hunt down the exact underlying type (for clock_t in one
case, it was under 6 layers of typedefs and macros), but that would be
for a specific set of headers.
For system headers, somebody could be using a header with different
definitions. For user-libraries, it might be a slightly different
version.
What is the problem you want to solve here?
The problem is that C expects an exact format-code when trying to use
*printf functions, and for that you need to know the exact types of
the expressions being passed. For example:
uintptr_t x; // from above examples
double y; //
printf("x * y is %?", x * y); // What's '?'
So are you asking because you don't know what Tim's construction does
with these types, or because you want to know if there is a portable and safe way to print out any arithmetic type, or because you are perfectly aware that C's printf has limitations and you want to post about how terrible C is and how great your own language is?
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),
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".
On 04/02/2026 17:12, David Brown wrote:
On 04/02/2026 17:44, Bart wrote:
On 04/02/2026 14:22, Tim Rentsch wrote:
Bart <bc@freeuk.com> writes:
On 03/02/2026 15:47, Tim Rentsch wrote:[...]
If variable 'u' is declared as uint32_t, a way to print it that is >>>>>> easy and also type-safe is
printf( " u is %lu\n", u+0LU );
What about a compound expression of several variables of mixed
integer types, possibly even mixed with floats, some of whose types
might either be conditional (depending on some macro), or opaque?
What is an example of a conditional/macro-dependent type?
Example from SDL2:
#if defined(_MSC_VER) && (_MSC_VER < 1600)
...
#ifndef _UINTPTR_T_DEFINED
#ifdef _WIN64
typedef unsigned __int64 uintptr_t;
#else
typedef unsigned int uintptr_t;
...
Example from SQLITE3:
#ifdef SQLITE_OMIT_FLOATING_POINT
# define double sqlite3_int64
#endif
Also what sort of opaque types do you have in mind?
Things like time_t and clock_t, or the equivalent from libraries.
Yes you could hunt down the exact underlying type (for clock_t in one
case, it was under 6 layers of typedefs and macros), but that would
be for a specific set of headers.
For system headers, somebody could be using a header with different
definitions. For user-libraries, it might be a slightly different
version.
What is the problem you want to solve here?
The problem is that C expects an exact format-code when trying to use
*printf functions, and for that you need to know the exact types of
the expressions being passed. For example:
uintptr_t x; // from above examples
double y; //
printf("x * y is %?", x * y); // What's '?'
So are you asking because you don't know what Tim's construction does
with these types, or because you want to know if there is a portable
and safe way to print out any arithmetic type, or because you are
perfectly aware that C's printf has limitations and you want to post
about how terrible C is and how great your own language is?
I was reponding to the example of a single variable with ONE type, that happens to be uint32_t, apparently a standard C type.
Yes maybe that particular strategy might work (you know it is an integer
and that it is unsigned).
But it doesn't solve the general problem: even if there is a single type involved, it might be conditional or opaque (or its type is changed
required all format codes to be revised.
Or there is an expression of mixed types.
and you want to post about how
terrible C is and how great your own language is?
I think pretty much every language except C seems to have solved this.
The point of both Tim's and Keith's solutions is that you do /not/
need to know the exact type of the expression you are printing - C's
conversion rules let them work with a range of different original types.
OK.
They were both picked specifically for the case of "uint32_t", but
can handle more types. Tim's can be used for any integer type of rank
up to "unsigned long int" (and thus not "long long" types),
So not such a great range.
while Keith's will
be fine with any integer type and any floating point type as long as
the value of the integer part of the floating point value is within
the range of "unsigned long int".
Better, *if* you know the expression has an unsigned integer type.
So as far as I'm concerned, the general problem remains. There are only workarounds and special cases that every user has to work out for themselves.
Meanwhile C11 (_Generic) and C23 ("%w" formats) don't appear to have
made much impact. It's not fixing it at the right level. But at least
you can now have a 999-bit type that you probably can't print even if
you wrote "%w999d"; or can you?
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.
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.
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.)
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.
On Wed, 4 Feb 2026 18:11:34 +0000
Bart <bc@freeuk.com> wrote:
On 04/02/2026 17:12, David Brown wrote:
> and you want to post about how
> terrible C is and how great your own language is?
I think pretty much every language except C seems to have solved this.
How do you do it in Fortran?
Also, there are many languages that "solved" it at very high cost of primitivity of their formatting features. E.g. Pascal.
I don't remember where Ada stands in this picturee. In case of Ada95 or newer, more like don't know rather then don't remember.
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 '?'
Bart <bc@freeuk.com> writes:
[...]
The problem is that C expects an exact format-code when trying to use
*printf functions, and for that you need to know the exact types of
the expressions being passed. For example:
uintptr_t x; // from above examples
double y; //
printf("x * y is %?", x * y); // What's '?'
Since you asked...
'?' is 'f' (or 'g' or 'e', or 'a', or any of those in upper case).
`x * y` is of type double.
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?
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.
On 04/02/2026 23:39, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
[...]
The problem is that C expects an exact format-code when trying to use
*printf functions, and for that you need to know the exact types of
the expressions being passed. For example:
uintptr_t x; // from above examples
double y; //
printf("x * y is %?", x * y); // What's '?'
Since you asked...
'?' is 'f' (or 'g' or 'e', or 'a', or any of those in upper case).
`x * y` is of type double.
The 'from above examples' applies to both x and y. That means that
'double' /may/ have been redefined like this (from my post):
#ifdef SQLITE_OMIT_FLOATING_POINT
# define double sqlite3_int64
#endif
I don't know what 'sqlite3_int64' is, but it sounds like a signed integer.
I was asked to give examples of conditional types, and thought it best
to do so from actual programs.
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 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.
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 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.
On 05/02/2026 00:52, Bart wrote:
On 04/02/2026 23:39, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
[...]
The problem is that C expects an exact format-code when trying to use
*printf functions, and for that you need to know the exact types of
the expressions being passed. For example:
uintptr_t x; // from above examples
double y; //
printf("x * y is %?", x * y); // What's '?'
Since you asked...
'?' is 'f' (or 'g' or 'e', or 'a', or any of those in upper case).
`x * y` is of type double.
The 'from above examples' applies to both x and y. That means that
'double' /may/ have been redefined like this (from my post):
#ifdef SQLITE_OMIT_FLOATING_POINT
# define double sqlite3_int64
#endif
I don't know what 'sqlite3_int64' is, but it sounds like a signed
integer. I was asked to give examples of conditional types, and
thought it best to do so from actual programs.
What you have found is an idiocy in SQLITE, not a problem in the C
language or printf. If the macro "SQLITE_OMIT_FLOATING_POINT" is
defined, then the type named "sqlite3_int64" is not an integer type,
nor can it hold arbitrary 64-bit integers (as Michael S pointed out,
and I assume accurately, it can hold 53 bit integers). I do not know
what this type is used for in the code, but something like
"sqlite3_bignum" would be a far better choice of name. And if it is
intended that people print out these values directly, defining "PRsqlite3_bignum" to "%g" or "%llu" as appropriate would be helpful.
(Yes, the resulting printf statements would be ugly - better ugly and
correct than wrong).
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!
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?
Bart <bc@freeuk.com> writes:
If I compile this code with 'gcc -Wall -Wextra -Wpedantic':
#include <stdio.h>
int main() {
int a = -1;
printf("%u", a);
}
it says nothing. The program displays 4294967295 instead of -1.
The behavior is unsurprising. The lack of a warning is very mildly inconvenient.
On 04/02/2026 14:22, Tim Rentsch wrote:
Bart <bc@freeuk.com> writes:
On 03/02/2026 15:47, Tim Rentsch wrote:
[...]
If variable 'u' is declared as uint32_t, a way to print it that is
easy and also type-safe is
printf( " u is %lu\n", u+0LU );
What about a compound expression of several variables of mixed
integer types, possibly even mixed with floats, some of whose types
might either be conditional (depending on some macro), or opaque?
What is an example of a conditional/macro-dependent type?
Example from SDL2:
#if defined(_MSC_VER) && (_MSC_VER < 1600)
...
#ifndef _UINTPTR_T_DEFINED
#ifdef _WIN64
typedef unsigned __int64 uintptr_t;
#else
typedef unsigned int uintptr_t;
...
Example from SQLITE3:
#ifdef SQLITE_OMIT_FLOATING_POINT
# define double sqlite3_int64
#endif
Also what sort of opaque types do you have in mind?
Things like time_t and clock_t, or the equivalent from libraries.
Yes you could hunt down the exact underlying type (for clock_t in one
case, it was under 6 layers of typedefs and macros), but that would be
for a specific set of headers.
For system headers, somebody could be using a header with different definitions. For user-libraries, it might be a slightly different
version.
What is the problem you want to solve here?
The problem is that C expects an exact format-code when trying to use
*printf functions, and for that you need to know the exact types of
the expressions being passed. For example:
uintptr_t x; // from above examples
double y; //
printf("x * y is %?", x * y); // What's '?'
David Brown <david.brown@hesbynett.no> writes:
[...]
How many people do you know who have actually written and use a
C11 print system using _Generic and variadic macros? I don't know
any. (I've written simple examples as proofs of concept, posted
in this group, but not for real use.) It turns out that people
/don't/ have to have workarounds. "printf" has its limitations -
there's no doubt there. But it is good enough for most people
and most uses.
I recently played around with an attempted framework using _Generic.
The goal was to be able to write something like
print(s(x), s(y), s(z));
where x, y, and z can be of more or less arbitrary types (integer, floating-point char*). The problem I ran into was that only one of
the generic associations is evaluated (which one is determined at
compile time), but *all* of them have to be valid code.
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.
David Brown <david.brown@hesbynett.no> writes:
[...]
How many people do you know who have actually written and use a
C11 print system using _Generic and variadic macros? I don't know
any. (I've written simple examples as proofs of concept, posted
in this group, but not for real use.) It turns out that people
/don't/ have to have workarounds. "printf" has its limitations -
there's no doubt there. But it is good enough for most people
and most uses.
I recently played around with an attempted framework using _Generic.
The goal was to be able to write something like
print(s(x), s(y), s(z));
where x, y, and z can be of more or less arbitrary types (integer, floating-point char*). The problem I ran into was that only one of
the generic associations is evaluated (which one is determined at
compile time), but *all* of them have to be valid code. There's a
proposal to change this for C 202y.
I didn't spend a lot of time on it.
David Brown <david.brown@hesbynett.no> writes:
On 05/02/2026 00:52, Bart wrote:
On 04/02/2026 23:39, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
[...]
The problem is that C expects an exact format-code when trying to use >>>>> *printf functions, and for that you need to know the exact types of
the expressions being passed. For example:
uintptr_t x; // from above examples
double y; //
printf("x * y is %?", x * y); // What's '?'
Since you asked...
'?' is 'f' (or 'g' or 'e', or 'a', or any of those in upper case).
`x * y` is of type double.
The 'from above examples' applies to both x and y. That means that
'double' /may/ have been redefined like this (from my post):
#ifdef SQLITE_OMIT_FLOATING_POINT
# define double sqlite3_int64
#endif
I don't know what 'sqlite3_int64' is, but it sounds like a signed
integer. I was asked to give examples of conditional types, and
thought it best to do so from actual programs.
What you have found is an idiocy in SQLITE, not a problem in the C
language or printf. If the macro "SQLITE_OMIT_FLOATING_POINT" is
defined, then the type named "sqlite3_int64" is not an integer type,
nor can it hold arbitrary 64-bit integers (as Michael S pointed out,
and I assume accurately, it can hold 53 bit integers). I do not know
what this type is used for in the code, but something like
"sqlite3_bignum" would be a far better choice of name. And if it is
intended that people print out these values directly, defining
"PRsqlite3_bignum" to "%g" or "%llu" as appropriate would be helpful.
(Yes, the resulting printf statements would be ugly - better ugly and
correct than wrong).
The macro doesn't define "sqlite3_int64", which as far as I can tell is always an integer type. It redefines "double".
That macro in isolation does seem deeply silly, but I haven't worked on sqlite3's source code. Apparently the authors found it convenient. Presumably anyone working on the source code has to keep in mind that
the word "double" doesn't necessarily mean what it normally means. It's
not the way I would have written it. I probably would have defined a
type name that can be either "double" or "sqlite3_int64", depending on
the setting of SQLITE_OMIT_FLOATING_POINT. But I don't know enough
about the sqlite3 source code to be able to meaningfully criticize it.
In almost all contexts, it's perfectly reasonable to assume that the
word "double" in C code refers to the predefined floating-point type.
Bart <bc@freeuk.com> writes:
[...]
/Some/ compilers with /some/ options will /sometimes/ tell you when
you've got it wrong.
But you first have to make an educated guess, or put in some dummy
format code.
Eventually, it will compile. Until someone else builds your program,
using a slightly different set of headers where certain types are
defined, and then it might either give compiler messages that they
have to fix, or it show wrong results.
That's not how I do it, and I don't think it's how most programmers do
it.
I know the rules well enough that I can usually write a correct format
string in the first place. If I make a mistake, gcc's warnings are a
nice check.
On 2026-02-06 06:10, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
If I compile this code with 'gcc -Wall -Wextra -Wpedantic':
#include <stdio.h>
int main() {
int a = -1;
printf("%u", a);
}
it says nothing. The program displays 4294967295 instead of -1.
Yes. You instruct 'printf' with '%u' to interpret and display it
(the variable 'a') as unsigned. ('-1' is not an unsigned numeric representation.) - I wonder what you are thinking here.
The behavior is unsurprising. The lack of a warning is very mildly
inconvenient.
Indeed unsurprising. And I even don't see any inconvenience given
that even an initialized declaration of 'unsigned a = -1;' is not
considered a problem in "C". I rather learned that to be a useful
code pattern when programming in "C".
On 06/02/2026 05:10, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
[...]
/Some/ compilers with /some/ options will /sometimes/ tell you when
you've got it wrong.
But you first have to make an educated guess, or put in some dummy
format code.
Eventually, it will compile. Until someone else builds your
program, using a slightly different set of headers where certain
types are defined, and then it might either give compiler messages
that they have to fix, or it show wrong results.
That's not how I do it, and I don't think it's how most programmers
do it.
I know the rules well enough that I can usually write a correct
format string in the first place. If I make a mistake, gcc's
warnings are a nice check.
I guess you've never used printf-family functions via the FFI of
another language!
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:[...]
I recently played around with an attempted framework using _Generic.
The goal was to be able to write something like
print(s(x), s(y), s(z));
where x, y, and z can be of more or less arbitrary types (integer,
floating-point char*). The problem I ran into was that only one of
the generic associations is evaluated (which one is determined at
compile time), but *all* of them have to be valid code.
That is annoying but it shouldn't be too hard to work around
it. To verify that hypothesis I wrote this test case:
#include <stdio.h>[30 lines deleted]
#include <time.h>
#include <stdint.h>
#include "h/show.h"
int
main(){
show([23 lines deleted]
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
foo = "foo"
bas = (const char *) "bas"
On Fri, 6 Feb 2026 12:39:55 +0000
Bart <bc@freeuk.com> wrote:
On 06/02/2026 05:10, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
[...]
/Some/ compilers with /some/ options will /sometimes/ tell you when
you've got it wrong.
But you first have to make an educated guess, or put in some dummy
format code.
Eventually, it will compile. Until someone else builds your
program, using a slightly different set of headers where certain
types are defined, and then it might either give compiler messages
that they have to fix, or it show wrong results.
That's not how I do it, and I don't think it's how most programmers
do it.
I know the rules well enough that I can usually write a correct
format string in the first place. If I make a mistake, gcc's
warnings are a nice check.
I guess you've never used printf-family functions via the FFI of
another language!
Vararg via FFI? Is it really a good idea?
Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:[...]
[30 lines deleted]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(){
show([23 lines deleted]
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
foo = "foo"
bas = (const char *) "bas"
Were you planning to show us what show.h looks like?
On Fri, 6 Feb 2026 12:39:55 +0000
Bart <bc@freeuk.com> wrote:
On 06/02/2026 05:10, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
[...]
/Some/ compilers with /some/ options will /sometimes/ tell you when
you've got it wrong.
But you first have to make an educated guess, or put in some dummy
format code.
Eventually, it will compile. Until someone else builds your
program, using a slightly different set of headers where certain
types are defined, and then it might either give compiler messages
that they have to fix, or it show wrong results.
That's not how I do it, and I don't think it's how most programmers
do it.
I know the rules well enough that I can usually write a correct
format string in the first place. If I make a mistake, gcc's
warnings are a nice check.
I guess you've never used printf-family functions via the FFI of
another language!
Vararg via FFI? Is it really a good idea?
I guess you've never used printf-family functions via the FFI of
another language!
Bart <bc@freeuk.com> writes:
[...]
I guess you've never used printf-family functions via the FFI of
another language!
As it happens, I haven't.
I presume if there were a point, you would have made it by now.
On 06/02/2026 13:08, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
[...]
I guess you've never used printf-family functions via the FFI of
another language!
As it happens, I haven't.
I presume if there were a point, you would have made it by now.
I thought you might have infered it.
All shortcomings of C can apparently be fixed by employing a selection
of it gcc's 200+ options.
Bart <bc@freeuk.com> writes:
[...]
/Some/ compilers with /some/ options will /sometimes/ tell you when
you've got it wrong.
But you first have to make an educated guess, or put in some dummy
format code.
Eventually, it will compile. Until someone else builds your program,
using a slightly different set of headers where certain types are
defined, and then it might either give compiler messages that they
have to fix, or it show wrong results.
That's not how I do it, and I don't think it's how most programmers do
it.
I know the rules well enough that I can usually write a correct format
string in the first place. If I make a mistake, gcc's warnings are a
nice check.
If I compile this code with 'gcc -Wall -Wextra -Wpedantic':
#include <stdio.h>
int main() {
int a = -1;
printf("%u", a);
}
it says nothing. The program displays 4294967295 instead of -1.
The behavior is unsurprising. The lack of a warning is very mildly >inconvenient.
On 06/02/2026 13:08, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
[...]
I guess you've never used printf-family functions via the FFI ofAs it happens, I haven't.
another language!
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.
Bart <bc@freeuk.com> writes:
On 06/02/2026 13:08, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
[...]
I guess you've never used printf-family functions via the FFI ofAs it happens, I haven't.
another language!
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
On 06/02/2026 19:21, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
On 06/02/2026 13:08, Keith Thompson wrote:Nobody said any of that.
Bart <bc@freeuk.com> writes:
[...]
I guess you've never used printf-family functions via the FFI of
another language!
As it happens, I haven't.
I presume if there were a point, you would have made it by now.
I thought you might have infered it.
All shortcomings of C can apparently be fixed by employing a selection
of it gcc's 200+ options. So no point in making the language better
instead.
But that doesn't work when some bits of raw C need to be used from
another language. For example, library headers containing a C API,
which some languages may use as the basis for their own bindings.
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.
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.
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?
On Fri, 6 Feb 2026 12:39:55 +0000
Bart <bc@freeuk.com> wrote:
On 06/02/2026 05:10, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
[...]
/Some/ compilers with /some/ options will /sometimes/ tell you when
you've got it wrong.
But you first have to make an educated guess, or put in some dummy
format code.
Eventually, it will compile. Until someone else builds your
program, using a slightly different set of headers where certain
types are defined, and then it might either give compiler messages
that they have to fix, or it show wrong results.
That's not how I do it, and I don't think it's how most programmers
do it.
I know the rules well enough that I can usually write a correct
format string in the first place. If I make a mistake, gcc's
warnings are a nice check.
I guess you've never used printf-family functions via the FFI of
another language!
Vararg via FFI? Is it really a good idea?
(with-dyn-lib nil(deffi printf-int "printf" int (str : int)))
(printf-int "%d\n" 42)42
On 2026-02-06, Michael S <already5chosen@yahoo.com> wrote:
On Fri, 6 Feb 2026 12:39:55 +0000
Bart <bc@freeuk.com> wrote:
On 06/02/2026 05:10, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
[...]
/Some/ compilers with /some/ options will /sometimes/ tell you when
you've got it wrong.
But you first have to make an educated guess, or put in some dummy
format code.
Eventually, it will compile. Until someone else builds your
program, using a slightly different set of headers where certain
types are defined, and then it might either give compiler messages
that they have to fix, or it show wrong results.
That's not how I do it, and I don't think it's how most programmers
do it.
I know the rules well enough that I can usually write a correct
format string in the first place. If I make a mistake, gcc's
warnings are a nice check.
I guess you've never used printf-family functions via the FFI of
another language!
Vararg via FFI? Is it really a good idea?
I support it in TXR Lisp. However, it's limited in that the FFI
definition is nailed to a particular choice of arguments.
For instance we could make a function foo which takes two arguments:
a str and an int, and calls the variadic printf.
Then we can call (foo "%d" 42). It will call printf("%d", 42).
We cannot pass fewer or more than two arguments to foo, and they have to
be compatible with str and int.
Demo:
$ txr
This is the TXR Lisp interactive listener of TXR 302.
Quit with :quit or Ctrl-D on an empty line. Ctrl-X ? for cheatsheet.
(with-dyn-lib nil(deffi printf-int "printf" int (str : int)))
printf-int
(printf-int "%d\n" 42)42
3
42 is output; 3 is the result value (3 characters output).
The : syntax in the deffi macro call indicates the variadic list.
It's not the case that we can make a variadic Lisp function pass its arguments
as an arbitrarily long variadic list with arbitrary types to the wrapped FFI function. Fixed parameters must be declared after the colon.
A dynamic treatment could be arranged via a heavy weight wrapper mechanism which
dynamically analyzes the actual arguments, builds a libffi function descriptor
on the fly, then uses it to make the call; it could be wortwhile for someone, but I didn't implement such a thing. Metaprogramming tricks revolving around dynamically evaluating deffi are also possible.
On 2026-02-05, Bart <bc@freeuk.com> wrote:
On 05/02/2026 22:55, Janis Papanagnou wrote:
On 2026-02-05 18:42, Bart wrote:
On 05/02/2026 11:41, David Brown wrote:
No, the /compiler/ has to work it out. Whether /you/ need to work it >>>>> out or not, depends on what you are doing with the result.
The compiler will not tell you the format codes to use!
Well, it seems the compiler I have here does it quite verbosely...
$ cc -o prtfmt prtfmt.c
prtfmt.c: In function ‘main’:
prtfmt.c:8:19: warning: format ‘%d’ expects argument of type ‘int’, but
argument 2 has type ‘double’ [-Wformat=]
8 | printf ("%d\n", f);
| ~^ ~
| | |
| int double
| %f
prtfmt.c:9:19: warning: format ‘%f’ expects argument of type ‘double’,
but argument 2 has type ‘int’ [-Wformat=]
9 | printf ("%f\n", i);
| ~^ ~
| | |
| | int
| double
| %d
...giving information of every kind - here for two basic types, but
it has also the same verbose diagnostics with the '_t' types I tried
(e.g. suggesting '%ld' for a 'time_t' argument).
Note: I'm still acknowledging the unfortunate type/formatter-coupling
notwithstanding.
/Some/ compilers with /some/ options will /sometimes/ tell you when
you've got it wrong.
That's an excellent reason to keep the bulk of your code portable, and
offer it to multiple compilers.
I think the only way you are going to run into a crappy compiler in a
real job situation in 2026 is if you're an embedded developer working
with some very proprietary processor for which the only compiler comes
from its vendor. Even so the bits of your code not specific to that
chip can be compiled with something else. Which you want to do not just
for diagnostics but to be able to run unit tests on that code on a
regular developer machine.
Eventually, it will compile. Until someone else builds your program,
using a slightly different set of headers where certain types are
defined, and then it might either give compiler messages that they have
to fix, or it show wrong results.
If I compile this code with 'gcc -Wall -Wextra -Wpedantic':
#include <stdio.h>
int main() {
int a = -1;
printf("%u", a);
}
it says nothing. The program displays 4294967295 instead of -1.
For that you need this:
$ gcc -Wall -pedantic -W -Wformat -Wformat-signedness printf.c
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.
On 07/02/2026 18:07, Kaz Kylheku wrote:
On 2026-02-06, Michael S <already5chosen@yahoo.com> wrote:
On Fri, 6 Feb 2026 12:39:55 +0000
Bart <bc@freeuk.com> wrote:
On 06/02/2026 05:10, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
[...]
/Some/ compilers with /some/ options will /sometimes/ tell you when >>>>>> you've got it wrong.
But you first have to make an educated guess, or put in some dummy >>>>>> format code.
Eventually, it will compile. Until someone else builds your
program, using a slightly different set of headers where certain
types are defined, and then it might either give compiler messages >>>>>> that they have to fix, or it show wrong results.
That's not how I do it, and I don't think it's how most programmers
do it.
I know the rules well enough that I can usually write a correct
format string in the first place. If I make a mistake, gcc's
warnings are a nice check.
I guess you've never used printf-family functions via the FFI of
another language!
Vararg via FFI? Is it really a good idea?
I support it in TXR Lisp. However, it's limited in that the FFI
definition is nailed to a particular choice of arguments.
For instance we could make a function foo which takes two arguments:
a str and an int, and calls the variadic printf.
Then we can call (foo "%d" 42). It will call printf("%d", 42).
We cannot pass fewer or more than two arguments to foo, and they have to
be compatible with str and int.
Demo:
$ txr
This is the TXR Lisp interactive listener of TXR 302.
Quit with :quit or Ctrl-D on an empty line. Ctrl-X ? for cheatsheet.
(with-dyn-lib nil(deffi printf-int "printf" int (str : int)))
printf-int
(printf-int "%d\n" 42)42
3
42 is output; 3 is the result value (3 characters output).
The : syntax in the deffi macro call indicates the variadic list.
It's not the case that we can make a variadic Lisp function pass its arguments
as an arbitrarily long variadic list with arbitrary types to the wrapped FFI >> function. Fixed parameters must be declared after the colon.
There's another issue with calling variadic functions, unrelated to the number of args. I can't tell from the above whether it's convered.
Normally an arg that is passed in a register, will be passed in GPR for integer, or a floating point register if not.
But a variadic float argument has to be passed in both, so for Win64
ABI/x64 it might be in both rcx and xmm1. I think it is similar on SYS V
for both x64 and arm64 (maybe on the latter both are passed in the GPR;
I'd have to go and look it up).
A dynamic treatment could be arranged via a heavy weight wrapper mechanism which
dynamically analyzes the actual arguments, builds a libffi function descriptor
on the fly, then uses it to make the call; it could be wortwhile for someone,
but I didn't implement such a thing. Metaprogramming tricks revolving around
dynamically evaluating deffi are also possible.
My LIBFFI approach just uses assembly; it's the simplest way to do it.
(The LIBFFI 'C' library also uses assembly to do the tricky bits.)
There, for Win64 ABI, I found it easiest to just load all the register
args to both integer and float registers, whether the called function
was variadic or not. That's far more efficient than figuring out the
right register argument by argument.
I haven't implemented that for SYS V; that's more of a nightmare ABI
where up to 6-12 args (8-16 on aarch64) can be passed between int and
float registers depending on the mix of types.
On Win64 ABI, it is 4 args, always.
Bart <bc@freeuk.com> wrote:
On 07/02/2026 18:07, Kaz Kylheku wrote:
The : syntax in the deffi macro call indicates the variadic list.
It's not the case that we can make a variadic Lisp function pass its arguments
as an arbitrarily long variadic list with arbitrary types to the wrapped FFI
function. Fixed parameters must be declared after the colon.
There's another issue with calling variadic functions, unrelated to the
number of args. I can't tell from the above whether it's convered.
Normally an arg that is passed in a register, will be passed in GPR for
integer, or a floating point register if not.
But a variadic float argument has to be passed in both, so for Win64
ABI/x64 it might be in both rcx and xmm1. I think it is similar on SYS V
for both x64 and arm64 (maybe on the latter both are passed in the GPR;
I'd have to go and look it up).
In SYS V convention argument is passed in exactly one place. It may
be GPR, may be XMM register, may be on the stack. If you put right
thing in RAX, then your arguments are valid regardless if the function
is a vararg function or not.
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.
and hence buggy code anyway.
On 06/02/2026 12:47, Michael S wrote:
On Fri, 6 Feb 2026 12:39:55 +0000
Bart <bc@freeuk.com> wrote:
On 06/02/2026 05:10, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
[...]
/Some/ compilers with /some/ options will /sometimes/ tell you
when you've got it wrong.
But you first have to make an educated guess, or put in some
dummy format code.
Eventually, it will compile. Until someone else builds your
program, using a slightly different set of headers where certain
types are defined, and then it might either give compiler
messages that they have to fix, or it show wrong results.
That's not how I do it, and I don't think it's how most
programmers do it.
I know the rules well enough that I can usually write a correct
format string in the first place. If I make a mistake, gcc's
warnings are a nice check.
I guess you've never used printf-family functions via the FFI of
another language!
Vararg via FFI? Is it really a good idea?
It's covered by platform ABIs of both Windows and SYS V.
This is printf called from an interpreted language with dynamic
typing:
a := 12345678987654321
b := pi
c := "A"*10
d := &a
printf("%lld %f %s %p\n", a, b, c, d)
Output is:
12345678987654321 3.141593 AAAAAAAAAA 00000000036A1D48
(Strings are converted to zero-terminated form for the FFI.)
If I try it like this however:
printf("%lld %f %s %p\n", d, c, b, a)
It will go wrong (crashing inside the C library). With the built-in
print feature, that doesn't happen:
println a, b, c, d
println d, c, b, a
On Fri, 6 Feb 2026 13:04:34 +0000
Bart <bc@freeuk.com> wrote:
On 06/02/2026 12:47, Michael S wrote:
On Fri, 6 Feb 2026 12:39:55 +0000
Bart <bc@freeuk.com> wrote:
I guess you've never used printf-family functions via the FFI of
another language!
Vararg via FFI? Is it really a good idea?
[...][...]
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?
On Fri, 6 Feb 2026 13:04:34 +0000
Bart <bc@freeuk.com> wrote:
On 06/02/2026 12:47, Michael S wrote:
On Fri, 6 Feb 2026 12:39:55 +0000
Bart <bc@freeuk.com> wrote:
On 06/02/2026 05:10, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
[...]
/Some/ compilers with /some/ options will /sometimes/ tell you
when you've got it wrong.
But you first have to make an educated guess, or put in some
dummy format code.
Eventually, it will compile. Until someone else builds your
program, using a slightly different set of headers where certain
types are defined, and then it might either give compiler
messages that they have to fix, or it show wrong results.
That's not how I do it, and I don't think it's how most
programmers do it.
I know the rules well enough that I can usually write a correct
format string in the first place. If I make a mistake, gcc's
warnings are a nice check.
I guess you've never used printf-family functions via the FFI of
another language!
Vararg via FFI? Is it really a good idea?
It's covered by platform ABIs of both Windows and SYS V.
My question was not about technical possibility. I understand that with enough of effort everything is possible.
I asked whether it is a good idea.
Is not it simpler for you and for your potential users to declare that
your language can not call external C functions with variable number of arguments? To me it does not sound like this ability is either necessary
or very valuable.
Above I assume that we are talking about your scripting language.
W.r.t. your other language, I have no strong opinion. But my weak
opinion is that it also does not need it, possibly with exception for
ability to do few (very few, hopefully) historically idiotically
defined Unix system calls that can be handled individually as special
cases.
Michael S <already5chosen@yahoo.com> wrote:
On Fri, 6 Feb 2026 13:04:34 +0000
Bart <bc@freeuk.com> wrote:
On 06/02/2026 12:47, Michael S wrote:
On Fri, 6 Feb 2026 12:39:55 +0000
Bart <bc@freeuk.com> wrote:
On 06/02/2026 05:10, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
[...]
/Some/ compilers with /some/ options will /sometimes/ tell you
when you've got it wrong.
But you first have to make an educated guess, or put in some
dummy format code.
Eventually, it will compile. Until someone else builds your
program, using a slightly different set of headers where certain >>>>>>> types are defined, and then it might either give compiler
messages that they have to fix, or it show wrong results.
That's not how I do it, and I don't think it's how most
programmers do it.
I know the rules well enough that I can usually write a correct
format string in the first place. If I make a mistake, gcc's
warnings are a nice check.
I guess you've never used printf-family functions via the FFI of
another language!
Vararg via FFI? Is it really a good idea?
It's covered by platform ABIs of both Windows and SYS V.
My question was not about technical possibility. I understand that with
enough of effort everything is possible.
I asked whether it is a good idea.
Is not it simpler for you and for your potential users to declare that
your language can not call external C functions with variable number of
arguments? To me it does not sound like this ability is either necessary
or very valuable.
Above I assume that we are talking about your scripting language.
W.r.t. your other language, I have no strong opinion. But my weak
opinion is that it also does not need it, possibly with exception for
ability to do few (very few, hopefully) historically idiotically
defined Unix system calls that can be handled individually as special
cases.
Well, some important interfaces depend on varargs functions. And
while you may handle such cases via user-written wrappers, it is
much nicer if FFI machinery handles varargs.
On 07/02/2026 22:48, Waldek Hebisch wrote:
Bart <bc@freeuk.com> wrote:
On 07/02/2026 18:07, Kaz Kylheku wrote:
The : syntax in the deffi macro call indicates the variadic list.
It's not the case that we can make a variadic Lisp function pass its arguments
as an arbitrarily long variadic list with arbitrary types to the wrapped FFI
function. Fixed parameters must be declared after the colon.
There's another issue with calling variadic functions, unrelated to the
number of args. I can't tell from the above whether it's convered.
Normally an arg that is passed in a register, will be passed in GPR for
integer, or a floating point register if not.
But a variadic float argument has to be passed in both, so for Win64
ABI/x64 it might be in both rcx and xmm1. I think it is similar on SYS V >>> for both x64 and arm64 (maybe on the latter both are passed in the GPR;
I'd have to go and look it up).
In SYS V convention argument is passed in exactly one place. It may
be GPR, may be XMM register, may be on the stack. If you put right
thing in RAX, then your arguments are valid regardless if the function
is a vararg function or not.
I had to go and check this, and you're right. SYS V does nothing special when calling variadic functions.
I guess that makes implementing the body of variadic functions harder,
since it doesn't know where to look for the n'th variadic argument
unless it knows the type.
And even then, because the int and non-int args are spilled to separate blocks, it has to keep track of where the next arg is in which block.
I think MS made the better call here; the necessary code is trivial for Win64 ABI.
A dynamic treatment could be arranged via a heavy weight wrapper mechanism which
dynamically analyzes the actual arguments, builds a libffi function descriptor
on the fly, then uses it to make the call; it could be wortwhile for someone,
but I didn't implement such a thing. Metaprogramming tricks revolving around
dynamically evaluating deffi are also possible.
My LIBFFI approach just uses assembly; it's the simplest way to do it.
(The LIBFFI 'C' library also uses assembly to do the tricky bits.)
There, for Win64 ABI, I found it easiest to just load all the register
args to both integer and float registers, whether the called function
was variadic or not. That's far more efficient than figuring out the
right register argument by argument.
I haven't implemented that for SYS V; that's more of a nightmare ABI
where up to 6-12 args
(Actually, 6-14 args; 6 max in GPRs and 8 in xmm regs)
(8-16 on aarch64) can be passed between int and
float registers depending on the mix of types.
On Win64 ABI, it is 4 args, always.
My code works fine for SYS V on amd64 and arm32. I do not think FFI
for aarch64 will be any harder, but ATM I do not have code generator
for aarch64, no need for FFI there.
I did not bother with Windows, since I do not use it it would be
untested and hence buggy code anyway.
I started generating code for ARM64, but gave up because it was too hard
and not fun (the RISC processor turned out to be a LOT more complex than
the CISC x64!).
The last straw was precisely to do with the SYS V call-conventions, and
I hadn't even gotten to variadic arguments yet, nor to structs passed by-value, where the rules are labyrinthine.
(I understand that neither LLVM or Cranelist backends support them
directly; they need to be dealt with earlier on, so the user of those
IRs needs to figure it out.)
and hence buggy code anyway.
I think you'd find it much simpler.
On Fri, 6 Feb 2026 13:04:34 +0000
Bart <bc@freeuk.com> wrote:
Vararg via FFI? Is it really a good idea?
It's covered by platform ABIs of both Windows and SYS V.
My question was not about technical possibility. I understand that with enough of effort everything is possible.
I asked whether it is a good idea.
Is not it simpler for you and for your potential users to declare that
your language can not call external C functions with variable number of arguments? To me it does not sound like this ability is either necessary
or very valuable.
Above I assume that we are talking about your scripting language.
W.r.t. your other language, I have no strong opinion. But my weak
opinion is that it also does not need it, possibly with exception for
ability to do few (very few, hopefully) historically idiotically
defined Unix system calls that can be handled individually as special
cases.
On 08/02/2026 17:50, Waldek Hebisch wrote:
Michael S <already5chosen@yahoo.com> wrote:
On Fri, 6 Feb 2026 13:04:34 +0000
Bart <bc@freeuk.com> wrote:
On 06/02/2026 12:47, Michael S wrote:
On Fri, 6 Feb 2026 12:39:55 +0000
Bart <bc@freeuk.com> wrote:
On 06/02/2026 05:10, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
[...]
/Some/ compilers with /some/ options will /sometimes/ tell you >>>>>>>> when you've got it wrong.
But you first have to make an educated guess, or put in some
dummy format code.
Eventually, it will compile. Until someone else builds your
program, using a slightly different set of headers where certain >>>>>>>> types are defined, and then it might either give compiler
messages that they have to fix, or it show wrong results.
That's not how I do it, and I don't think it's how most
programmers do it.
I know the rules well enough that I can usually write a correct
format string in the first place. If I make a mistake, gcc's
warnings are a nice check.
I guess you've never used printf-family functions via the FFI of
another language!
Vararg via FFI? Is it really a good idea?
It's covered by platform ABIs of both Windows and SYS V.
My question was not about technical possibility. I understand that with
enough of effort everything is possible.
I asked whether it is a good idea.
Is not it simpler for you and for your potential users to declare that
your language can not call external C functions with variable number of
arguments? To me it does not sound like this ability is either necessary >>> or very valuable.
Above I assume that we are talking about your scripting language.
W.r.t. your other language, I have no strong opinion. But my weak
opinion is that it also does not need it, possibly with exception for
ability to do few (very few, hopefully) historically idiotically
defined Unix system calls that can be handled individually as special
cases.
Well, some important interfaces depend on varargs functions. And
while you may handle such cases via user-written wrappers, it is
much nicer if FFI machinery handles varargs.
There are only a few standard C functions that are variadic, but they
are quite important ones (with the *printf family at the top). But
there is little doubt that C's handling of variadic functions is very unsafe, and implementations are often challenging (the SysV ABI for
x86-64 is awkward and complex for variadic functions - the Win64 ABI is easier for variadics at the expense of making many other function calls
less efficient). It is a weak point in the design of C.
And for other languages trying to access C code via a FFI, variadic functions are going to be inefficient, complicated, and unsafe (unless
you are /very/ sure of the calling parameters). If your alternative language is safer, neater, or easier to write than C (and if it isn't,
why bother with it?), you'll want a better way to handle formatted
printing than C's printf.
On 08/02/2026 17:50, Waldek Hebisch wrote:
Michael S <already5chosen@yahoo.com> wrote:
On Fri, 6 Feb 2026 13:04:34 +0000
Bart <bc@freeuk.com> wrote:
On 06/02/2026 12:47, Michael S wrote:
On Fri, 6 Feb 2026 12:39:55 +0000
Bart <bc@freeuk.com> wrote:
On 06/02/2026 05:10, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
[...]
/Some/ compilers with /some/ options will /sometimes/ tell you >>>>>>>> when you've got it wrong.
But you first have to make an educated guess, or put in some
dummy format code.
Eventually, it will compile. Until someone else builds your
program, using a slightly different set of headers where certain >>>>>>>> types are defined, and then it might either give compiler
messages that they have to fix, or it show wrong results.
That's not how I do it, and I don't think it's how most
programmers do it.
I know the rules well enough that I can usually write a correct
format string in the first place. If I make a mistake, gcc's
warnings are a nice check.
I guess you've never used printf-family functions via the FFI of
another language!
Vararg via FFI? Is it really a good idea?
It's covered by platform ABIs of both Windows and SYS V.
My question was not about technical possibility. I understand that with
enough of effort everything is possible.
I asked whether it is a good idea.
Is not it simpler for you and for your potential users to declare that
your language can not call external C functions with variable number of
arguments? To me it does not sound like this ability is either necessary >>> or very valuable.
Above I assume that we are talking about your scripting language.
W.r.t. your other language, I have no strong opinion. But my weak
opinion is that it also does not need it, possibly with exception for
ability to do few (very few, hopefully) historically idiotically
defined Unix system calls that can be handled individually as special
cases.
Well, some important interfaces depend on varargs functions. And
while you may handle such cases via user-written wrappers, it is
much nicer if FFI machinery handles varargs.
There are only a few standard C functions that are variadic, but they
are quite important ones (with the *printf family at the top). But
there is little doubt that C's handling of variadic functions is very unsafe, and implementations are often challenging (the SysV ABI for
x86-64 is awkward and complex for variadic functions - the Win64 ABI is easier for variadics at the expense of making many other function calls
less efficient). It is a weak point in the design of C.
And for other languages trying to access C code via a FFI, variadic functions are going to be inefficient, complicated, and unsafe (unless
you are /very/ sure of the calling parameters). If your alternative language is safer, neater, or easier to write than C (and if it isn't,
why bother with it?), you'll want a better way to handle formatted
printing than C's printf.
On 08/02/2026 17:55, David Brown wrote:
On 08/02/2026 17:50, Waldek Hebisch wrote:
Michael S <already5chosen@yahoo.com> wrote:
On Fri, 6 Feb 2026 13:04:34 +0000
Bart <bc@freeuk.com> wrote:
On 06/02/2026 12:47, Michael S wrote:
On Fri, 6 Feb 2026 12:39:55 +0000
Bart <bc@freeuk.com> wrote:
On 06/02/2026 05:10, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
[...]
/Some/ compilers with /some/ options will /sometimes/ tell you >>>>>>>>> when you've got it wrong.
But you first have to make an educated guess, or put in some >>>>>>>>> dummy format code.
Eventually, it will compile. Until someone else builds your
program, using a slightly different set of headers where certain >>>>>>>>> types are defined, and then it might either give compiler
messages that they have to fix, or it show wrong results.
That's not how I do it, and I don't think it's how most
programmers do it.
I know the rules well enough that I can usually write a correct >>>>>>>> format string in the first place. If I make a mistake, gcc's >>>>>>>> warnings are a nice check.
I guess you've never used printf-family functions via the FFI of >>>>>>> another language!
Vararg via FFI? Is it really a good idea?
It's covered by platform ABIs of both Windows and SYS V.
My question was not about technical possibility. I understand that with >>>> enough of effort everything is possible.
I asked whether it is a good idea.
Is not it simpler for you and for your potential users to declare that >>>> your language can not call external C functions with variable number of >>>> arguments? To me it does not sound like this ability is either
necessary
or very valuable.
Above I assume that we are talking about your scripting language.
W.r.t. your other language, I have no strong opinion. But my weak
opinion is that it also does not need it, possibly with exception for
ability to do few (very few, hopefully) historically idiotically
defined Unix system calls that can be handled individually as special
cases.
Well, some important interfaces depend on varargs functions. And
while you may handle such cases via user-written wrappers, it is
much nicer if FFI machinery handles varargs.
There are only a few standard C functions that are variadic, but they
are quite important ones (with the *printf family at the top). But
there is little doubt that C's handling of variadic functions is very
unsafe, and implementations are often challenging (the SysV ABI for
x86-64 is awkward and complex for variadic functions - the Win64 ABI
is easier for variadics at the expense of making many other function
calls less efficient). It is a weak point in the design of C.
And for other languages trying to access C code via a FFI, variadic
functions are going to be inefficient, complicated, and unsafe (unless
you are /very/ sure of the calling parameters). If your alternative
language is safer, neater, or easier to write than C (and if it isn't,
why bother with it?), you'll want a better way to handle formatted
printing than C's printf.
For about the last 30 years I've used the C library to do i/o, as it was simpler than Win32 calls. (I didn't even know it was supposed to be for
C; it just look like an easier library with shorter function names, that also shipped with Windows.)
So while I do do most of my own conversion and formating, actual i/o,
and a few cases such as floating point which are fiddly, uses calls like this:
sprintf(s, "%f", x)
sscanf(str, "%lf%n", &x, &numlength)
printf("%.*s", length, s)
fprintf(f, "%.*s", length, s)
The last two are generally called when I have a buffer-full of output.
The sscanf call is the only I ever time use *scanf functions.
In the prior 15 years of course, I did have to do everything.
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).
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.
Bart <bc@freeuk.com> wrote:
On 07/02/2026 22:48, Waldek Hebisch wrote:
In SYS V convention argument is passed in exactly one place. It may
be GPR, may be XMM register, may be on the stack. If you put right
thing in RAX, then your arguments are valid regardless if the function
is a vararg function or not.
I had to go and check this, and you're right. SYS V does nothing special
when calling variadic functions.
Well, there is special thing: RAX should contain number of SSE
registers used for passing parameters. You do not need to set
RAX for normal calls (at least on Linux, some other systems
require it for all calls).
I 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.
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:
On 08/02/2026 19:21, Waldek Hebisch wrote:
Bart <bc@freeuk.com> wrote:
On 07/02/2026 22:48, Waldek Hebisch wrote:
In SYS V convention argument is passed in exactly one place. It may
be GPR, may be XMM register, may be on the stack. If you put right
thing in RAX, then your arguments are valid regardless if the function >>>> is a vararg function or not.
I had to go and check this, and you're right. SYS V does nothing special >>> when calling variadic functions.
Well, there is special thing: RAX should contain number of SSE
registers used for passing parameters. You do not need to set
RAX for normal calls (at least on Linux, some other systems
require it for all calls).
I looked out for that but don't remember seeing in on godbolt.org, and I think it was for SYS V.
But I tried it again, and AL is being set to some count, which appears
to be the total number of float arguments (and rereading your comment,
you say the same thing).
I guess that makes implementing the body of variadic functions harder,
since it doesn't know where to look for the n'th variadic argument
unless it knows the type.
Well, if a function wants to do actual computation with an argument
it should better know its type.
On Windows, it will know the location of the next vararg and can access
its value before it knows the type. The user-provided type (eg.
'var_arg(p, int)') can simple do a type-punning cast on the value.
All args: fixed, variadic-reg, variadic-pushed, will also all be in consecutive stack slots, regardless of type (This is the real reason why floats should be loaded to GPRs for variadics: entry code just needs to spill those 4 GPRs, it anyway won't know the mix of types.)
I started generating code for ARM64, but gave up because it was too hard >>> and not fun (the RISC processor turned out to be a LOT more complex than >>> the CISC x64!).
Well, RISC processor means that compiler have to do work which is
frequently done by hardware on a CISC. Concerning arm32, most
annoying for me was limited range of constants, especially limit
on offsets that can be part of an instruction. With my current
implementation that puts something like 2kB limit on size of local
variables. And my generator mixes instructions and constant data
(otherwise it could not access constant data using limited available
offsets), which works but compilcates code generator and probably
gives suboptimal performance.
There are a dozen annoying things like this on arm64. Even when you give
up and decide to load 64-bit constants from a memory pool, you find you can't even directly access that pool as it has an absolute address. That
can involve first loading the page address (ie. minus lower 12 bits) to
R, then you have to use an address mode involving R and the lower 12
bits as an offset.
The last straw was precisely to do with the SYS V call-conventions, and
I hadn't even gotten to variadic arguments yet, nor to structs passed
by-value, where the rules are labyrinthine.
My low-level code only handles scalar arguments. That includes pointer
to structures, but not structures passed by value. Structures passed by
value could be handled by higher-level code, but up to now there was
no need to do this.
BTW, my amd64 code is assembler, so off-topic here, but arm32 code
is mostly C. I use two helper structures:
struct registers_buffer {
int i_reg[4];
union {double d; struct {float sl; float sh;} sf2;} f_reg[8];
};
typedef struct registers_buffer reg_buff;
typedef struct arg_state { int ni; int sfi; int dfi; int si;} arg_state;
C code fills 'reg_buff' with values and later low-level assembly
copies values from the buffer to registers. I allocate enough space on
the stack so that C code can write to the stack without risk of
stack overflow.
There are 3 helper routines:
This looks pretty complicated, but what is it for: is it still to do
with variadic functions, or is to with the LIBFFI problem?
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?
Bart <bc@freeuk.com> wrote:
On 08/02/2026 19:21, Waldek Hebisch wrote:
Bart <bc@freeuk.com> wrote:
On 07/02/2026 22:48, Waldek Hebisch wrote:
In SYS V convention argument is passed in exactly one place. It may >>>>> be GPR, may be XMM register, may be on the stack. If you put right
thing in RAX, then your arguments are valid regardless if the function >>>>> is a vararg function or not.
I had to go and check this, and you're right. SYS V does nothing special >>>> when calling variadic functions.
Well, there is special thing: RAX should contain number of SSE
registers used for passing parameters. You do not need to set
RAX for normal calls (at least on Linux, some other systems
require it for all calls).
I looked out for that but don't remember seeing in on godbolt.org, and I
think it was for SYS V.
But I tried it again, and AL is being set to some count, which appears
to be the total number of float arguments (and rereading your comment,
you say the same thing).
I guess that makes implementing the body of variadic functions harder, >>>> since it doesn't know where to look for the n'th variadic argument
unless it knows the type.
Well, if a function wants to do actual computation with an argument
it should better know its type.
On Windows, it will know the location of the next vararg and can access
its value before it knows the type. The user-provided type (eg.
'var_arg(p, int)') can simple do a type-punning cast on the value.
All args: fixed, variadic-reg, variadic-pushed, will also all be in
consecutive stack slots, regardless of type (This is the real reason why
floats should be loaded to GPRs for variadics: entry code just needs to
spill those 4 GPRs, it anyway won't know the mix of types.)
I started generating code for ARM64, but gave up because it was too hard >>>> and not fun (the RISC processor turned out to be a LOT more complex than >>>> the CISC x64!).
Well, RISC processor means that compiler have to do work which is
frequently done by hardware on a CISC. Concerning arm32, most
annoying for me was limited range of constants, especially limit
on offsets that can be part of an instruction. With my current
implementation that puts something like 2kB limit on size of local
variables. And my generator mixes instructions and constant data
(otherwise it could not access constant data using limited available
offsets), which works but compilcates code generator and probably
gives suboptimal performance.
There are a dozen annoying things like this on arm64. Even when you give
up and decide to load 64-bit constants from a memory pool, you find you
can't even directly access that pool as it has an absolute address. That
can involve first loading the page address (ie. minus lower 12 bits) to
R, then you have to use an address mode involving R and the lower 12
bits as an offset.
As I wrote I generate constant pool as part of instruction stream.
I use PC-relative adressing so as long as constant is close
enough to instruction using it I can use short offsets.
There is some extra effort, normally I am trying to put constants
after unconditional jump and before next label, but I may need
extra jump to "jump around" constants.
The last straw was precisely to do with the SYS V call-conventions, and >>>> I hadn't even gotten to variadic arguments yet, nor to structs passed
by-value, where the rules are labyrinthine.
My low-level code only handles scalar arguments. That includes pointer
to structures, but not structures passed by value. Structures passed by >>> value could be handled by higher-level code, but up to now there was
no need to do this.
BTW, my amd64 code is assembler, so off-topic here, but arm32 code
is mostly C. I use two helper structures:
struct registers_buffer {
int i_reg[4];
union {double d; struct {float sl; float sh;} sf2;} f_reg[8];
};
typedef struct registers_buffer reg_buff;
typedef struct arg_state { int ni; int sfi; int dfi; int si;} arg_state; >>>
C code fills 'reg_buff' with values and later low-level assembly
copies values from the buffer to registers. I allocate enough space on
the stack so that C code can write to the stack without risk of
stack overflow.
There are 3 helper routines:
This looks pretty complicated, but what is it for: is it still to do
with variadic functions, or is to with the LIBFFI problem?
This is to handle a call, it does not matter variadic or not.
The call is from dynamically typed language and argument types are
known only at runtime
(actually, argument types _may_ be statically
known at higher level, but for simplicity "all" (see below) calls go
trough a single low-level routine that handles general dynamic case. Dispatcher routine (which I did not show) loops over arguments,
decodes their types and converts them to C representation. Then
it calls one of the 3 helper routines to place each argument in the buffer
or on the stack.
There is simpler integer only code which is mainly used to perform
low level system calls. This iterface do not convert arguments
(the assumption is that caller passes C-compatible representation)
and logic is simpler as it just puts what fits in registers and
the rest on the stack.
Both interfaces spill all registers used by calling language to
the stack before actual processing of the call. This is because
C code may perform a callback and callback may trigger garbage
On 09/02/2026 01:27, Waldek Hebisch wrote:
Bart <bc@freeuk.com> wrote:
On 08/02/2026 19:21, Waldek Hebisch wrote:
Bart <bc@freeuk.com> wrote:
On 07/02/2026 22:48, Waldek Hebisch wrote:
The last straw was precisely to do with the SYS V call-conventions, and >>>>> I hadn't even gotten to variadic arguments yet, nor to structs passed >>>>> by-value, where the rules are labyrinthine.
My low-level code only handles scalar arguments. That includes pointer >>>> to structures, but not structures passed by value. Structures passed by >>>> value could be handled by higher-level code, but up to now there was
no need to do this.
BTW, my amd64 code is assembler, so off-topic here, but arm32 code
is mostly C. I use two helper structures:
struct registers_buffer {
int i_reg[4];
union {double d; struct {float sl; float sh;} sf2;} f_reg[8];
};
typedef struct registers_buffer reg_buff;
typedef struct arg_state { int ni; int sfi; int dfi; int si;} arg_state; >>>>
C code fills 'reg_buff' with values and later low-level assembly
copies values from the buffer to registers. I allocate enough space on >>>> the stack so that C code can write to the stack without risk of
stack overflow.
There are 3 helper routines:
This looks pretty complicated, but what is it for: is it still to do
with variadic functions, or is to with the LIBFFI problem?
This is to handle a call, it does not matter variadic or not.
The call is from dynamically typed language and argument types are
known only at runtime
OK, so it is probably performing the LIBFFI thing, or rather what needs
to come before, which is to marshall the arguments into homogenous array
of values. The actual LIBFFI task needs some ASM support as you say.
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.)
Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
[...]
I recently played around with an attempted framework using _Generic.
The goal was to be able to write something like
print(s(x), s(y), s(z));
where x, y, and z can be of more or less arbitrary types (integer,
floating-point char*). The problem I ran into was that only one of
the generic associations is evaluated (which one is determined at
compile time), but *all* of them have to be valid code.
That is annoying but it shouldn't be too hard to work around
it. To verify that hypothesis I wrote this test case:
#include <stdio.h>
#include <time.h>
#include <stdint.h>
#include "h/show.h"
int
main(){
[30 lines deleted]
show(
uc,sc,us,ss,ui,si,ul,sl,ull,sll,
c,f,d,ld,yes,no,u16,s16,uge32,sge32,
runtime,now,offset,uf32,sf32,
c * now / 1e8 * ld,
foo, bas
);
printf( "\n" );
return 0;
}
which compiles under C11 and (along with the show.h include file)
produces output:
uc = 255
sc = -1
us = 65535
[23 lines deleted]
foo = "foo"
bas = (const char *) "bas"
Were you planning to show us what show.h looks like?
| Sysop: | DaiTengu |
|---|---|
| Location: | Appleton, WI |
| Users: | 1,097 |
| Nodes: | 10 (0 / 10) |
| Uptime: | 21:13:14 |
| Calls: | 14,089 |
| Files: | 187,111 |
| D/L today: |
1,307 files (436M bytes) |
| Messages: | 2,490,428 |