RU version is available. Content is displayed in original English for accuracy.
Advertisement
Advertisement
⚡ Community Insights
Discussion Sentiment
64% Positive
Analyzed from 2763 words in the discussion.
Trending Topics
#bits#size#bit#integer#types#int#long#type#sizes#fixed

Discussion (47 Comments)Read Original on HackerNews
Nonetheless, I started to use C for programming only in 1990, when I got access to the Microsoft C and Borland Turbo C compilers.
At that time, 36 years ago, the C flexible integer sizes were already obsolete.
Since that time until now, while using C on a great variety of computers, from servers and workstations to the smallest microcontrollers, I have seen plenty of portability problems created by the existence of the flexible integer sizes.
The only programs that had no portability problems were those that never used the flexible integer sizes, but only integers with a definite size, e.g. 8-bit, 16-bit, 32-bit or 64-bit.
While sizeof solves the problems of memory allocation or copying, it does not help in preventing unexpected integer overflows, because even the size of "char" may be unknown, and even if the size of "char" is known, writing code with multiple paths that would check or prevent overflow for different integer sizes is very cumbersome.
Flexible integer sizes would work well only on the old computers, where integer overflow generated a hardware exception, so installing an overflow handler would have been sufficient to make the C code work correctly regardless of the size of the native integers.
So there has been a really long time in C's evolution where we haven't had fixed size types which has been a super annoying mess of #ifdefs in portable code.
The variable size ints have allowed some super weird architectures though. I remember looking at the datasheet for the Motorola 56000 DSP and noting that the C compiler set char = short = int = 24 bits! That was because the hardware could not address anything smaller than 24 bits. I think long could be 48 bits.
This is a highly ignorant comment. You're confusing the fact that you only had to work with a single target architecture with the whole concept of multiple processor architectures being somehow obsolete, as if there was a sudden law of nature that forced every single computer, being full blown HPC stuff or small microcontrollers used in embedded applications.
Take a look at arduino. They still have 16-bit models out there. Also noteworthy, it seems some DSPs also have ints larger than 32 bits.
For instance if your integer arithmetic needs to happen with 32 bit precision (no matter if the code runs on a 16- or 32-bit CPU), there is no scenario where using 'int' makes sense. Instead you'd use a fixed-width 32-bit integer type and accept that math operations are compiled into two instructions on a 16-bit CPU.
And OTH if you only require 16 bits integer width, there's not much point in picking a 32 bit integer type. Since two's-complement integer encoding has been standard since at least the 70s, the CPU can do narrow operations in the native register width. Any overflow/wraparound is still correct when only looking at the lowest 16-bits of the result.
Before 1990, I had worked with a variety of ISAs, from IBM mainframes and DEC minicomputers to many kinds of microprocessors.
After 1990, I have used C on a great variety of x86, Motorola 68xxx, IBM/Motorola PowerPC and many generations of ARM ISAs.
Even if you use explicit 32-bit integers in a program, that will not create any correctness problem when the program is run on 16-bit microcontroller. At most such a program may have a suboptimal performance. Performance problems are much easier solved during porting than obscure bugs.
There have been some popular DSPs with 24-bit integers, e.g. Motorola 56xxx. Nonetheless, nobody would want to run on such a DSP a program that was written for another kind of CPU, even for another kind of DSP, because the performance would be pathetic. Any program for such a fixed-point DSP, even when derived from an existing program, would need to be rewritten while using at every point in the program the knowledge that the size of "int" is 24 bits (because the programs for fixed-point DSPs need copious amounts of scaling operations, to avoid overflows and underflows), so such a program should not actually use "int", but it should typedef an "int24_t", to make this assumption explicit.
I can see the argument for "an int works on the natural machine size". But it starts getting a lot more complicated when you have structs - suddenly byte positions are very important (as is 'sizeof' :), and if you're running the same code on different platforms, and using pointers to access them, well you need to be careful...
Fixed-size types (and we've more or less given up on non-power-of-2 sized primitive types) force you to think about the size of the type you're using at the point of creation, and if you really do want 'an int is the size of the local machine', you're free to 'typedef u32 int;' in a platform-specific file - I deliberately did not use 'int', 'short', 'long' etc. in the language.
[1] https://compile-xc.org/compiler/language/types/
I agree with the article of course. I think most confusion comes not having learned about the purpose of having them be defined based on the architecture in the first place. It took me a long time before I stumbled upon how they really worked and why, because while I started it was either x86 or nothing. When x64 showed up, suddenly it became relevant and everybody started learning about C types more in depth as they ran into issues with sizeof.
Also, misuse in data protocols is where I think the bad reputation of the flexible type sizes came from. stdint was desperately needed for that reason and it came a bit late.
C would probably not have survived unless it had this flexibility.
But its not justa historical thing. Today there are modern platforms like DSPs that have 32bit sized char, because that is the smallest addressable type. These platforms depend on C for tool chains, even if most "portable" C wont run correctly on them. The fact that you can build hardware like that, and not have to invent a new language / dialect to program them is a huge win for the world.
<edit> I didnt see the footnote about DSPs at first read </edit>
On modern computers, it is impossible to write correct C programs that are agnostic about the true size in bits of the "flexible" types char, short, int, long and long long.
If your program must depend on assumptions about the size in bits of the integer types, those assumptions must be made explicit, by using types like int16_t, int32_t etc.
Writing correct programs that are agnostic about the integer sizes is possible only in programming languages that allow the programmer to install an integer overflow handler even if the CPU does not generate a hardware exception for that, in which case the compiler must insert appropriate overflow checking instructions that would invoke the installed handler when necessary.
This problem did not exist on old computers, where there were hardware exceptions for integer overflows, so even in C you could install a signal handler for SIGFPE, which would also be invoked by integer overflows.
C was pretty much ignored on 8 bit home computers, outside some toy compilers for CP/M.
In the 16 bit days, it was yet another language alongside BASIC compilers, Pascal, Modula-2, Assembly.
C is so tied to UNIX, that POSIX had to be created so that any non-UNIX operating system could provide a cozy home for their C compilers.
UNIX/POSIX is for all practical purposes the runtime most C applications rely on, there are naturally some exceptions like free-standing or Windows (which eventually gave up and add to start improving its support).
It is only due to historical accident that Microsoft gave up on Xenix, instead of replacing their MS-DOS efforts.
C was invented to rewrite UNIX in a programming language that made it easy to port UNIX between machines.
So what you're saying is contradictory. You're saying the underlying motivation of C was wrong or unnecessary (porting UNIX to different hardware architectures) but C won because that underlying motivation (easy porting between hardware architectures) was partially right.
Your position is now that C didn't need UNIX as a stopgap, which is weird because your argument gains no weight (basically saying C's dominance is sheer coincidence) if it's true but if it's false you're just plain wrong.
This is why I think so many C developers have no clue what they are doing. They just take whatever decision was made in C as gospel instead of thinking of everything being up for negotiation.
Shouldn't they be 64 bits on most modern systems then?
Arguably so, but then one would lose the ability to natively name 16-bit integer types because "short" would be 32 bits.
An earlier comment addresses x86-64. AArch64 (pedantically, the A64 instruction set used for AArch64's 64-bit execution mode) is similar, in that addresses are 64 bits wide but ALU instructions typically encode a width bit, called "sf", that selects either 32- or 64-bit data registers and arithmetic. See, for example, https://arm.jonpalmisc.com/latest_aarch64/add_addsub_ext .
Not sure how what you said makes sense.
But porting 32-bit code to 64-bit was a big deal back then, and C99 with its new fixed-width integer types overlapped with the first AMD64 CPUs (and Microsoft's MSVC didn't start to support C99 until around 2015 anyway), I guess keeping int on 32-bits in the popular compilers was deemed 'safer' for porting existing code. I guess we can already be lucky that all the big compilers agreed on the same int width.
On Intel 286 we had a 16-bit machine word and 24-bit addresses. A pointer wasn't just two machine words concatenated - the upper 8 bits were stored somewhere else - a segment register.
On modern machines we don't (usually) need to consider this because we have a single linear virtual address space, though the size is architecture dependant - usually above 40 bits and below 64. Most common size is 48-bits, but also up to 57-bits with 5 level paging enabled.
Either way we round up to 64-bits to store the pointer as one integer. C optionally provides types `intptr_t` and `uintptr_t`, which are integers large enough to hold the value of a pointer. Converting a pointer to `intptr_t` and back to the pointer type results in a pointer that compares equal to the original.
However, there is no guarantee that a pointer converted to `intptr_t` and back to a pointer can be dereferenced! It works most of the time because of our linear address space and non-use of segmentation, but segmentation can still be used - the FS and GS segment registers are still available on x86_64 and are commonly used for thread local storage. If you take a `thread_local T*`, convert it to `intptr_t`, and then convert it back to a `thread_local T*` on another thread and attempt to dereference it, then despite the pointers comparing equal, they dereference to different virtual addresses.
Integers tied to the size of a pointer would have been misguided. Pointers are not integers! (They just happen to use an integer in their representation).
Another one, `size_t` is supposed to represent the maximum size of any object. However, that's also not well-defined. The maximum object size on the Intel 256 would have been 16-bits, because that is all you can fit in a single segment.
On a modern machine, a `size_t` should really be 48-bits (4LP) or 57-bits (5LP), because we can't have an object larger than our maximum virtual address size - but `size_t` is typically 64-bits.
Rust originally says that its types usize and isize are the same size as pointers, but this was ret-conned in later Rust to say actually they're the same size as addresses for this reason.
I think the only real annoyance is that each programmer/team did it with their own convention (I32, INT32, i32, int32, WORD, Word bool, BOOL, Bool, etc., etc.); standardizing helps with putting everyone on the same page more than it helps porting. It doesn't prevent people from reverse-typedef-ing standard names to local "dialectal" names, though.
But I also think that one should only rarely use raw integer types, in an ideal world; the elephant in the room is that typedef is kind of the second "billion dollars mistake" [1]. C is a weakly typed language and there's no practical way to undo it (besides transpilation), so there's double no point to leave behind raw types.
[1] For those not too familiar with C, typedef defines a "type alias", not a type: https://en.cppreference.com/cpp/language/typedef
Well, Microsoft has considered C done for quite some time, and after C++20, they don't seem to be in a hurry to keep up with ISO either for C or C++ (there are discussions on support channels about customer relevant C++23 and C++26 features, none on C past C17), similar to how Apple and Google are handling their in box compilers as well.
This is the issue with how people talk about C. int is basically the signed version of size_t aka a word sized data type. It's not meant to have a fixed size.
When people want the classic 4 byte data type they should choose long instead.
It isn't. `int` is at least as large as `short` and at least 16-bits. On modern systems `int` is still typically 32-bits whereas `size_t` is typically 64-bits. There's a `ssize_t` in POSIX for signed sizes.
> When people want the classic 4 byte data type they should choose long instead.
`long` is only at least 4 bytes, and at least as large as `int`. On MSVC (LLP64 data model) it's 4 bytes, but on SYSV (LP64 data model) it's 8 bytes. `long` should almost never be used if you actually want portable code today.
`int` is 32-bits and `long long` is 64-bits on both LP64 and LLP64. If you want portable code using the native integer types, these are the ones you should use, definitely not `long`.
If you code exclusively using those types (and the `*ptr_t` ones) then you precisely express to both the compiler and the next person reading it what is supposed to be in those variables.