https://thephd.dev/c-the-improvements-june-september-virtual...
That makes parsing the unsigned variant relatively simple. Undefined behavior would be end-of-game, but unsigned addition and multiplication will not invoke that (if you need he,p, look at your favorite multiple-precision library for ideas about detecting wraparound)
For parsing signed integers, derive the min and max values of the signed variant from the max value of the unsigned one ((whatever_max - 1) / 2 or something like that
Then parse your input to the unsigned variant first, then check whether it fits in the unsigned version.
Someone's got to write a paper to get it in, hopefully based on some sort of existing practice.
If you want to know "what is the widest signed integer type available", you will get some kind of answer one way or another.
> A compiler vendor can set the sizes to whatever they want regardless of the target architecture and claim conformance if they meet the minimum.
Compilers claim conformance with other standards besides just the C standard.
Obviously, when someone says “type X is size Y on architecture Z”, they’re not talking about the C standard, they’re talking about some particular ABI.
There are multiple ABIs for x86_64. I think the major compilers use 32 bits for int and 64 for long, but there could very well be a compiler that used different sizes, with a different ABI.
Well, that code is not, and was not ever, standards compliant.
After all, if predictable behavior in the future across all possible configurations were a high priority requirement for the tools people use to make computers do things, neither C nor C++ would have gotten off the ground. Instead, people don't memorize the entire standard. They write code that works on their machine, publish it, fix the bugs when people say it doesn't work on their machine, and maybe converge towards something that is standards compliant but, more likely, they converge towards something that works on the standard implementation on 90% of the operating systems available.
Would you be able to name three examples?
My intention was to make a good case for old code being compiled today by third parties.
But to my understanding intmax_t was introduced in C99, before widespread dominance of 64 bits platforms. So it would not be surprising that there exists code with lower assumptions about its size.
But actually I made also another mistake, the major problem had little to do with faulty logic and more to do with dynamic linking [0]
I will see myself out today.
Anyone who uses intmax_t in a durable API (public function argument, member of public structure) is just a goof.
How would you compile that to machine code which would, when run, still give you the maximum integer on next year’s CPU with all-new super-duper wide integers?
It is more reasonable to think of a binary to be compiled to a specific architecture triplet, and if the CPU changes, the architecture must change, and therefore the ABI, and if the OS wants to run a binary from an older architecture, some emulation layer is needed.
Of course, this would be a lot of work for the operating system people if they want to run binaries from lots of what are now different architectures, and it was apparently easier to just force the C standard to abandon the entire concept of intmax_t being the largest.
intmax_t by intent is the maximum integer available on a given platform. Abandoning that idea is a disservice to compiler writers, programmers -- especially scientific programmers -- everywhere.
That would add a lot of friction to changing CPUs, and making distributing software more complicated.
- POSIX defines that in_port_t is equal to uint16_t, so for that type there's no problem at all ;)
- Don't use printf/scanf for this, roll your own
- Refuse to print/read uid_t that are outside intmax_t range
- Use an autoconf test
Until then I guess I’ll have to do something like
#if sizeof(pid_t) > sizeof(uintmax_t)
#error Nope
#endif
…repeated for all the types I need to parse and/or print.Also this doesn't just apply "in the future" -- integer types larger than intmax_t already exist, C23 just updates the standard to match reality.
#if SIZEOF_PID_T > SIZEOF_UINTMAX_T
...
#endif
Where you detect these sizes from the toolchain and deposit them as #define constants in some "config.h" header.There are ways to detect sizes by compiling a source file to an object file, and then analyzing the object file (no execution), so things work under cross-compiling.
I've used a number of tricks over the years, and settled on this one:
https://www.kylheku.com/cgit/txr/tree/configure?h=txr-278#n1...
The basic idea is that we can take a value like sizeof(long), which is a constant, and using nothing but constant arithmetic, we can convert it to the characters " 8". These characters can be placed into a character array where they are delimited by some prefix that we can look for in the compiled object file with some common utilities.
This is quite durable in the sense that it can be reasonably expected to work through a wide variety of object formats.
In that test program, I have a structure with some character arrays. The larger character arrays hold the informative prefixes. The two-byte arrays hold decimal digits for sizes. Two digits go up to 99 bytes so we are safe for a number of years.
The DEC macro calculates the ASCII decimal value of its argument, expanding to a two-byte initializer for a character array:
#define D(N, Z) ((N) ? (N) + '0' : Z)
#define UD(S) D((S) / 10, ' ')
#define LD(S) D((S) % 10, '0')
#define DEC(S) { UD(S), LD(S) }
E.g. DEC(42) -> /* the equivalent of */ { '4', '2' }
But actually DEC(42) -> { UD(42), LD(42) }
-> { D((42) / 10, ' '), D((42) % 10, '0') }
-> { ((42) / 10) ? ((42 / 10)) + '0' : ' ',
((42) % 10) ? ((42 % 10)) + '0' : '0' }
The space instead of a leading zero is so that if we pull this into shell arithmetic, it isn't confused for an octal number.Similarly for the second problem you can read digit by digit in reverse order and create the number using x = 10x + d while detecting overflow.
I'm not saying that this is a great thing to need to do, but the mentioned problems aren't particularly fundamental and the solutions are CS101 stuff.
Sure, someone might come up with an architecture where that's not true. But even in an intmax_t-is-really-the-largest-integer-type world [which hasn't been true for the major C compilers for the past decade or so!], I can almost assuredly come up with some compilation modes that still conform with whichever C version you use that still causes your code to fail horribly. I don't think there's any architecture where uid_t et al would exceed uint64_t, and until such an architecture comes to exist, it's quite frankly not worth caring about.
But the "physical" size of the number as represented in memory can change across platforms, no?
Complain to POSIX so they add printf macro defines for all their implementation-sized integer types by the year 2100.
"Portably" across what? You need to narrow down the problem space and better define it.
If it's portably across a myriad of processor architectures, hardware profiles and operating systems then you would need to take the intermediate representation route (like Java). There's very little else that can be done in the face of infinite, non-compatible ABIs at every layer.
> you would need to take the intermediate representation route (like Java). There's very little else that can be done in the face of infinite, non-compatible ABIs at every layer.
No, I don’t think so. There’s an obvious solution, dictated by the architectual choices made by C (i.e. every architecture which introduces a new larger integer type, by necessity redefines intmax_t, and creates a new ABI), but the community (for reasons of their own) did not want to do it that way, and instead broke the promise of intmax_t always being the largest type.
This creates a problem. How do you portably printf() an integer type that you don’t (and can’t) know the size of, like, say, a uid_t?
Before, when intmax_t was guaranteed to be the largest type, you could reliably and portably cast a uid_t userid to an intmax_t and printf() that: printf(PRIdMAX "\n", (intmax_t)userid);
But now, when any type might be larger than an intmax_t? What do you do?
(The same problem exists when reading an integer string; how do you read, say, a port number, when you don’t know what type in_port_t is? Previously, you could call strtoimax() on the string, detect overflow, and then cast the resulting intmax_t to in_port_t and finally compare for equality to make sure it wasn’t truncated.)
> There is active work in this area to allow us to transition to a better ABI and let these two types live up to their promises
Yes please. Introducing new wider integer types in your CPU, but pretending you haven’t created a new architecture with an associated new ABI, is silly.