C's Flexible Integer Sizes Were Not a Design Mistake

(pikuma.com)

26 points | by ibobev 1 day ago

10 comments

  • adastra22 4 minutes ago
    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.
  • adrian_b 27 minutes ago
    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.

    • locknitpicker 15 minutes ago
      > 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.

      • flohofwoe 10 minutes ago
        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 (or whatever is most efficient). Any overflow/wraparound is still correct when only looking at the lowest 16-bits of the result.

  • flowerbreeze 59 minutes ago
    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.

  • quelsolaar 55 minutes ago
    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>

    • pjmlp 32 minutes ago
      C survived because UNIX carried it.
      • flohofwoe 15 minutes ago
        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++).
  • usrnm 1 hour ago
    But not having fixed size integers (or integers tied to the size of a pointer) was. Both can be useful
    • tialaramex 19 minutes ago
      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.

    • quelsolaar 55 minutes ago
      At the time its was probably very hard to know what the fixed sizes should be.
      • pjmlp 31 minutes ago
        PL/I among other systems languages predating C, had the ability to explicitly define bit sizes for its types.
  • RobotToaster 41 minutes ago
    > 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?

    • userbinator 30 minutes ago
      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?"
    • flohofwoe 33 minutes ago
      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.

      • astrobe_ 19 minutes ago
        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.
    • Alpha3031 25 minutes ago
      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).
  • pjmlp 30 minutes ago
    Kind of, the mistake was not doing like PL/I where besides default machine specific sizes, the developer could explicitly assert the required sizes.
    • flohofwoe 23 minutes ago
      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.
    • Alpha3031 19 minutes ago
      I thought most implementations of C have stdint (intN_t, leastN_t and fastN_t etc).
  • lexicality 18 minutes ago
    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.

    • mmoll 10 minutes ago
      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.
      • lexicality 8 minutes ago
        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
  • lmz 22 minutes ago
    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.
  • gustavopezzi 17 hours ago
    Author here. Thanks for sharing.
    • enriquto 1 hour ago
      it's a great read!

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