Hacker News

Top stories

Live mirror
30 storiesupdated just nowView source snapshot
  1. Meta Blocks President Lula's Facebook Page, Campaign Ads 2 Weeks from Election (reddit.com)
    78comments
  2. Flip Fluid on Flip Dots (mitxela.com)
    10comments
  3. Does Georgism work? Five years later (astralcodexten.com)
    252comments
  4. OpenAI Feared "Optics" of what might appear on Hacker News (authorsguild.org)
    180comments
  5. Go Concurrency Distilled (antonz.org)
    85comments
  6. PipePipe: NewPipe hard fork implementing SponsorBlock (github.com/infinityloop1308)
    228comments
  7. DeepSeek Elastic Compute (DSec) (arxiv.org)
    84comments
  8. Show HN: Reladraw – A diagram language where you decide where to place things (github.com/reladraw)
    85comments
  9. Finally, A True Blue Rose Exists (sciencenews.org)
    —discuss
  10. Improving site performance by shipping more CSS (github.blog)
    29comments
  11. C's Flexible Integer Sizes Were Not a Design Mistake (pikuma.com)
    39comments
  12. The internet discovers TLA+. Now what? (reasonable.io)
    7comments
  13. A searchable library of forgotten public-domain film clips from 1915 onward (movingimagearchive.com)
    26comments
  14. Biology might not be quantum, but its math is quantumlike (quantamagazine.org)
    25comments
  15. ASML says it sold 'absolutely nothing' in Europe in 2026 (tomshardware.com)
    648comments
  16. Fifteen years later, the Apple Cards origin story (lexontech.org)
    103comments
  17. What is the size of Yemen? (2024) (theborys.substack.com)
    59comments
  18. An agent used DNS to reach an external chatbot (alignment.openai.com)
    105comments
  19. Drawgent: Coding agent on a live Excalidraw canvas (tangled.org/yanndegat.tngl.sh)
    41comments
  20. Evolving programming languages in the AI era (dashbit.co)
    58comments
  21. How I changed teaching after AI managed to do all my homework assignments (thelastsoftwareengineer.substack.com)
    192comments
  22. Promising discoveries about the potential for life on one of Saturn’s icy moons (fu-berlin.de)
    32comments
  23. Reverse-engineering the Intel 8087's tangent algorithm: more than CORDIC (righto.com)
    11comments
  24. Turning GLM-5.3-Flash into a Jev-like decision model (privatemode.ai)
    39comments
  25. Exploding variance of means of exponentials: least-squares to the rescue (francisbach.com)
    —discuss
  26. How to keep enjoying programming in a world of LLMs (haskell.org)
    271comments
  27. We Should Be Able to Change Our Languages (jimmyhmiller.com)
    22comments
  28. Modern Object Pascal Introduction for Programmers (castle-engine.io)
    75comments
  29. Generate fonts where every LLM token is the same width (mesh.host)
    12comments
  30. LP Voting and Investor Consent for AIFs (taghash.io)
    —discuss

C's Flexible Integer Sizes Were Not a Design Mistake

32 pointsby 1d agopikuma.com
36 comments
1h agoHN ↗

it's a great read!

would like to learn some tricks, like configuring gcc so that int is 64 bits, and so on

1h agoHN ↗

But not having fixed size integers (or integers tied to the size of a pointer) was. Both can be useful

1h agoHN ↗

At the time its was probably very hard to know what the fixed sizes should be.

1h agoHN ↗

PL/I among other systems languages predating C, had the ability to explicitly define bit sizes for its types.

1h agoHN ↗

It turns out that you don't want integers the same size as a pointer because somebody might squirrel away capability bits in your pointer type (see CHERI) and you definitely do not want integers with capability 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.

22m agoHN ↗

What is the size of a pointer though?

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.

1h agoHN ↗

Thank you for the article! Do I see a Turbo-C screenshot there or am I imagining it? It was my first IDE (I didn't know that's what it was called) when I started programming. I sometimes miss it, it was really good, especially the help system.

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.

1h agoHN ↗

Good article.

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>

1h agoHN ↗

I don't agree. UNIX was an extremely niche operating system until Linux won the data center, at that time both C and C++ were already extremely popular outside the UNIX world. C won because it was so easy to adapt to new hardware architectures (even GPU shading languages are just minimally extended flavours of C and C++).

40m agoHN ↗

Sorry, what? Most of data centers were running a UNIX operating system back in the day. What operating system do you think they were running?

17m agoHN ↗

Most people only cared about C, because they needed to work on UNIX, and UNIX was taking over the server room and all 1980's graphical workstations.

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.

20m agoHN ↗

This kind of flexibility is a purely historical thing.

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.

1h agoHN ↗

A 'plain' int object has the natural size suggested by the architecture of the execution environment.

Shouldn't they be 64 bits on most modern systems then?

1h agoHN ↗

It should indeed, and in hindsight it would have been better to move int to 64 bits (especially for C's integer promotion, which only really makes sense when the promotion happens to the register width.

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.

1h agoHN ↗

I've always believed that they kept 32 bits ints on 64 bits CPU as a default because going full 64 bits would make the code and data structures bigger for "no reason" (it's not often that one hits the 4.10^9 limit in system code (if you don't count timestamps, that is)). For instance, a load-register-with-immediate instruction would normally take 5 bytes (opcode+value) on 32 bits, but 9 bytes on 64.

1h agoHN ↗

On x86-64, you need an extra prefix to do 64-bit operations (while 64-bit addressing is the default), so it's a question of "are you sure you need the 64 bits and 32 isn't enough?"

1h agoHN ↗

Memory addresses being 64 bits due to needing to address more than 4 GiB memory doesn't mean most integer instructions operate most efficiently with 64 bits. Instructions for 32 bit integers are still more efficient than 64 bit, whereas 16 bit operands require a prefix byte meaning they're less compact and cache efficient (on AMD64 anyway).

36m agoHN ↗

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 .

1h agoHN ↗

Kind of, the mistake was not doing like PL/I where besides default machine specific sizes, the developer could explicitly assert the required sizes.

1h agoHN ↗

C99 kinda fixed that with the `(u)int_leastN_t` types (which are hardly used in practice though). And shame that it took Microsoft 16 years to even start supporting C99 though so we were basically forced to keep using our own custom integer typedefs long after the C standard had fixed the issue.

21m agoHN ↗

Agreed, but it could have been there since day one, given the languages in 1960's.

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.

1h agoHN ↗

I thought most implementations of C have stdint (intN_t, leastN_t and fastN_t etc).

43m agoHN ↗

These are C99 features which MSVC only got around 2015 (of course a decade later those are safe to use in portable code).

45m agoHN ↗

Is that really that much of a big deal, though? Before stdint, if one needed that level of accuracy, one would do your own equivalent of stdinit by hand, and adjust those definitions when porting to another compiler/platform. The same goes for your local boolean type.

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

20m agoHN ↗

The deal was dealing with #ifdef spaghetti to define all of those, especially when mixing libraries across platforms.

1h agoHN ↗

I agree that the C flexible integer sizes were still necessary at the time of its creation, when some important computers still had word sizes that were not powers of two.

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.

1h agoHN ↗

At that time, 36 years ago, the C flexible integer sizes were already obsolete.

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.

59m agoHN ↗

The parent is completely right in the sense that for actually portable C code it was always better to use fixed-width integer types which were chosen for the problem to solve instead of target hardware capabilities.

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.

48m agoHN ↗

As I have said, I have not worked with a single architecture.

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.

1h agoHN ↗

Meh. In today's world if exact sizes were not a requirement then you should use the int_fastN_t types to at least guarantee the width you are expecting instead of using the fixed size types (which may not be optimal) or using plain "int" which may be smaller than expected.

1h agoHN ↗

I feel like the article glosses over the fact that (to my mind) `int_fast32_t` and `int_least32_t` are a much better solution than "int is a random size good luck"

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.

59m agoHN ↗

I came here to say exactly that. There’s int_leastN_t for storage and int_fastN_t for computation. Stdint.h really gets a bad rap here.

57m agoHN ↗

fwiw depending on use cases you might actually want to be using the fast variants for storage too, for example on arm64 you'll get aligned memory loads

53m agoHN ↗

No one thinks that ptrdiff_t should be a fixed size. It is quite obviously the integer type you would get from subtracting two pointers, which is naturally tied to the word size of the machine you are using. C's original "int" type is what we would now call ptrdiff_t.