4 ms·
I started using C in the late 80s and have used C++ on and off since then. I've not been able to understand how it ever made sense to have a language where the
by chihuahua 3y ago
I started using C in the late 80s and have used C++ on and off since then. I've not been able to understand how it ever made sense to have a language where the size of int (and other types) varies depending on the platform and implementation.
I don't think I've ever been in a situation where I thought "I need this variable to have a type that's the 3rd smallest available, but I don't care how many bits it actually is. On some platforms, it will overflow at 32768, and on other platforms it will overflow at 2.1 billion, and I'm fine with either behavior."
Every time I choose a type for a variable, I'm thinking "32 bits is an appropriate size for this variable, and this other variable needs 64 bits".
I understand that these days we use int32_t etc, but why did the unpredictably-sized types ever make sense to anyone?
- smashed 3y agoPerformance and transparency to the underlying system. Int would be whatever the instruction set of the platform supports by default for most of its arithmetic and memory operations. C would be sitting a layer above assembly but not try to hide what is going on behind the scene I guess. Much later on languages like java would bring the concept of virtual machines where the types are unified across different instructions set.
- Zanni 3y agoYou're straw-manning a bit with your "3rd smallest type available." Int was supposed to be the natural word-size for the system, which, despite C being "portable," you had to know for efficiency reasons. Memory access at the time was slow, and if you specified an Int that you couldn't load/unload with a single instruction or fit into a register, you would kill your performance.
- chihuahua 3y agoThanks for the response. On one hand, it makes sense that the natural word size is relevant. On the other hand, that means it encourages writing non-portable code.
- jcranmer 3y ago> Memory access at the time was slow Actually, memory access was fast (assuming you're referring to the time when C was developed, not when ANSI C was standardized), as you could access any individual memory location in a single clock cycle. It's once you start seeing the development of RISC systems that being able to clock processors faster than memory starts becoming a thing, and that makes memory slow. > and if you specified an Int that you couldn't load/unload with a single instruction or fit into a register, you would kill your performance. This is the bigger issue. If you have to do lots of extra masking or adjustment of the values to emulate an N-bit integer that isn't otherwise available on your system, that's the slowdown.
- Dylan16807 3y agoInt in particular has that use. But there isn't a similar value in char and short and long being variable, so I wouldn't say it's a strawman. And int has stopped being a reliable machine word size, if it ever was reliable in the first place. Edit: I would say the most useful sizes are fixed_width and max(fixed_width, machine_word). And none of the native C types reliably fit into either of those categories.
- da_chicken 3y agoIf I were to speculate... I think you're really underestimating how limited hardware used to be. Firstly, portability of code was simply not as useful a feature. The big benefit was that the programmer could read and write code anywhere, not that the same code ran everywhere. Everything you could do with a computer was fundamentally hardware-dependent. If you needed to take your code and transfer it to another platform, you were going to have to deal with such a fundamentally different architecture that you already knew you'd have to rewrite it. Like storage wasn't even compatible. You didn't even know the width of a character, let alone the encoding for it. It could be 7 bits, 6 bits, 5 bits, whatever. RS232 communication might even be a struggle. C came about in 1972. The first widely available single chip UART was in 1971. Second, hardware was simply not capable enough to dynamically handle a fixed size. If you wanted a 16 bit integer, your hardware might not be able to do operations on that at all. You might have 6 bit integers and everything else is a bitwise stream. If you want 16 bit math, maybe you've got to figure out how to do that 6 bits at a time yourself. Some of the hardware you were dealing with was microchips, not microcontrollers. So you didn't do it because (a) it wasn't a concern for the programmer because portability wasn't really a thing in the same terms, and (b) it was outside the practical capabilities of the hardware.
- 0x09 3y agoIt's partly an artifact of BCPL where the only type was one representing the machine word. So with a word-sized type you don't get portability in the range of values the type can represent, but can portably know that you won't e.g. take up 2 registers by using it. You might consider the int_fastN_t types a sort of spiritual successor to this with fewer downsides since they purposefully guarantee a minimum width.
- dooglius 3y agoI'm not an expert on the history, but I think older processors didn't have say a divide instruction at a bunch of different widths, there'd be one special width and everything bigger had to break out into a more complex implementation based on the divide for the smaller width. Int was supposed to be that special width that was one-to-one with underlying instructions.
- epcoa 3y agoHardware divide has little to do with it. Historically, it is just the register width. Division is too complex and variable even within an ISA. On a side note, there are still micros in common use today, such as low end Cortex that don't have any hardware divider. And the lifecycles of embedded devices mean that even older architectures, but still less than 20 years old such as ARMv5 without hardware dividers are still being maintained. It's not that historic. The performance of hardware divide isn't something that follows simply from the "natural" register width of the processor anyway, it depends on the real estate and implementation given to it. > I think older processors didn't have say a divide instruction at a bunch of different widths But the common ones did, for example PPC and even the 8086. The reason for this is because division is slow and you get a benefit by using the smallest width possible. > special width that was one-to-one with underlying instructions That's usually just the width of the general purpose registers. Of course 64-bit came along and threw this out the window.
- ajross 3y ago> I don't think I've ever been in a situation where I thought "I need this variable to have a type that's the 3rd smallest available, but I don't care how many bits it actually is. On some platforms, it will overflow at 32768, and on other platforms it will overflow at 2.1 billion, and I'm fine with either behavior." No, but you've probably been in a situation where "I need this variable to fit in a register", no?
- chihuahua 3y agoOk, then it would make sense to have ONE datatype that fits a register. But it would make more sense that the other types (char, short, long) are a known and fixed size. Otherwise, what's the use case for short? "I need something that's smaller than a register, but I don't care what range it can store?"
- ajross 3y agoFWIW "short" has always been 16 bits. It was introduced with VAX Unix (and a few other 32 bit architectures) so that you could easily port code that assumed 16 bit arithmetic. The point isn't that, today, you'd choose to do what C did. But in 1977 there weren't a lot of other options. Early C did indeed have fixed, defined types. But moving to the VAX messed all that up. They had to choose to either add new types (and thus turn a bunch of code that would otherwise run fast on the VAX into weirdly-interpreted nonsense) or update the existing types so that idiomatic PDP-11 code still did what you expect most of the time. They chose the latter.
- IAmLiterallyAB 3y agoI agree. And I find it interesting that everyone seems to think the alternative is having a handful of hard coded sizes (8, 16, 32, 64). I don't think that's the answer either. I imagine a language without any concrete integer type, instead you have to parameterize. For example int<32> for a 32 bit integer. Syntax is debatable but that's the idea. It's great cuz it can work on even weird hardware, I can make an int<13> for 13 bit machines. And you can make typedefs for common stuff. Like a size_t mapping to int<32> or a word_t mapping to int<16>. Whatever you need. I've been meaning to make a compiler to test the idea out. Someday maybe
- jcranmer 3y agoC23 has _BitInt(N) that works exactly like I think you'd want it to work.
- IAmLiterallyAB 3y agoYes, and I'm excited about that! Though I do wish the syntax sucked less. Also would be interesting to see it be the primary integer type of the language. I'm also interested in a more advanced version for setting arbitrary bounds. E.g. an integer that goes from 1-10. Adding two of those would be an integer from 2-20. The goal being that the compiler could help enforce that you don't pass a value that is OOB. Might make more sense for that to be a library. Perhaps some C++ template magic. But I digress
- dragontamer 3y agoIn the 1970s, the size of a byte changed from platform-to-platform, with 6-bit bytes and 6-word (aka: 36-bit words) remaining popular. It is in this environment that C was invented, or at least its predecessor the B language as well as Unix. By the 1980s, C is getting standardized but I'm guessing programmers were still used to the different sized bytes (let alone different sized words and/or integers) that 4x8-bit vs 6x6-bit (and other such computers). True, the 1980s established the 8-bit standard, but a veteran programmer had no reason to believe that 8-bits was going to remain the standard for the next decade.
- jcranmer 3y agoYou're in luck. The C committee has a rationale document for C99 (https://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf https://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.1...) that explains why they did some of the things. In the beginning was int, which was a 16-bit integer, because that was the size of a register on PDP-11. Then char was added to represent individually addressable bytes in memory. The rationale explains that long was added to be able to represent 32-bit disk offsets. Since 32-bit arithmetic is slower on 16-bit systems (and 16-bit arithmetic can be slower on 32-bit systems!), you don't want to use that, so the solution was to add a 16-bit short, 32-bit long, and an int that is whichever is faster. The types aren't actually mandated to be 16-bit and 32-bit respectively because that would be awkward on existing 24-bit or 36-bit machines. In a mirror to this problem, the need by C99 for 64-bit offsets on 32-bit systems, combined with a serious existing corpus of code meant that you needed to create a new type for 64-bit, which was long long. But now you get the additional fun that it's not clear on a 64-bit system if long is 32 bits or 64 bits. In modern practice, most hardware is 32-bit or 64-bit (and anything that isn't pretty much requires code to be written from scratch specifically for it anyways), and all 64-bit processors make it no less performant to use 32-bit than 64-bit for computation. Thus i32-everywhere gives you both consistency and performance portability in a way that wasn't true in the 16-to-32 transition era. Of course, by the time you have the varying sized insanity, people who wanted code to be portable instead actually defined their own fixed-size types, which C99 also standardized into stdint.h.