7 ms·
I work with embedded systems daily, writing code that is expected to work on multiple different architectures (e.g. systems where char is 8-bit, 16-bit, 24-bit
by TheNewAndy 10y ago
I work with embedded systems daily, writing code that is expected to work on multiple different architectures (e.g. systems where char is 8-bit, 16-bit, 24-bit or 32-bit, systems without floating point units, systems with SIMD instructions, systems without, etc). In doing this, I have found that fixed width types make the job harder, and any code that uses fixed width types is generally more difficult to work with.
You have an int16_t? How do you port it to a machine without a 16-bit type? What would happen if you replaced it with a 32-bit type? If you are relying on wraparound, then your code is already undefined, so some compiler is likely going to mess with you anyway.
The stuff about communicating with the outside world is quite different. At this point you have protocols, and it depends on how a protocol is written, and how you are able to actually interact with the world to fulfil that protocol. If the protocol is an ABI, then generally you are fine, because the compiler too will match the ABI. If it is a file format, then you need to know how your file io functions work (do they just write the bottom 8-bits of a char, or do they blat the whole thing across?).
- lake99 10y ago> How do you port it to a machine without a 16-bit type? The problem is made more severe when you use "int" and let the compiler decide the size of the variable. I guess I don't know what you're advocating. > The stuff about communicating with the outside world is quite different. I meant communicating as a throwaway example. It's a valid example, but really, everything gets affected. For example, a CAN identifier is 29 bits. What generic "int" would you use when you want to fill in this identifier? And no, I work at a level that's lower than an ABI. So, no OS, no file IO, etc.
- adrianN 10y agoHow does it make portability worse if you write your code in a way that doesn't assume anything about the size of your ints? If you don't know that your ints are 16 or 32 or 27 bits long you have to be more careful with checking for overflows, but if the code does that I don't see what problems you'll have when you port it to a platform with a different int size.
- lake99 10y agoYou can only check for overflows if you err in favour of larger architectures and declare all integers as "long int", and then check for overflows. Even the 8-bit compilers I have seen will allocate 32-bits for long int. And doing that will let the code remain compatible on smaller architectures, but it will hammer your CPU and memory usage. It's not a pleasant situation when you're struggling to save bytes.
- TheNewAndy 10y agoYou can't ever check for (signed) overflows in C - you can only prevent them. I'm not sure if I'm drawing a distinction you weren't intending to make (if so, then my apologies). When you are adding signed integers in C, you need to prove that doing so can't overflow - knowing the exact width doesn't really help you here - just prove it for the narrowest possible width and you have proved it for all of them. In practice this isn't hard because either it is trivial to prove (e.g. incrementing a variable which is known to be small, or just plain modifying the code to put in explicit saturation/other logic for the cases where things get hairy) Of course an 8-bit compiler is going to allocate 32-bits for a long int - the language requires that a long is at least 32-bits wide. If you had a compiler that didn't do this, then it isn't a C compiler.
- TheNewAndy 10y agoIf you use "int", you know you have at least 16-bits to play with. Take some correct code that works with int16_t, and replace the "int16_t" with plain "int". What breaks? 1. If you are dumping it out to file or similar (i.e. assuming the layout in memory) then your code is no longer portable between different endian machines. 2. If you are relying on overflow behaviour, then your code is broken already (this doesn't apply to unsigned types, but using minimum width types and explicit mods/masks is likely going to be clearer code anyway). 3. The one place where you can't replace int16_t with int is in the case where you make use of the assumption that an int16_t can represent -0x8000 and you then port to a machine that doesn't use two's complement and int is 16-bits... I'm not aware of any such machine, but that is about the extent of it. What type to use for a 29-bit CAN identifier? A "long" will do just fine. It is guaranteed to be at least 32-bits wide, and also to exist. Would you suggest a uint29_t?
- lake99 10y ago> Take some correct code that works with int16_t, and replace the "int16_t" with plain "int". What breaks? Memory. Defaulting to 16-bits when an 8-bit variable will do can be incredibly wasteful on an 8-bit µC. Keep in mind that we are not just talking about one variable in isolation. We are talking about all the integers we pass between functions. We are talking about code space, data space, and stack space. There are compilers that can optimize their arithmetic operations to 8-bit registers when they can be sure that that's all their operands need. > Would you suggest a uint29_t? If you are aware of a machine that provides that, yes! Otherwise, I'd suggest uint32_t. Yes, "long" is guranteed to be 32-bits wide, but it can also be 64-bits wide. I would not recommend defaulting to "long", as that could be wasteful. Here's an interesting discussion about the meaning of "long" and "long long" for their compiler: https://www.dsprelated.com/showthread/comp.dsp/42108-1.php https://www.dsprelated.com/showthread/comp.dsp/42108-1.php. I see this discussion as a failure of the C standard. I would much rather the compiler provided int64_t or int40_t or whatever else they can, that is not inefficient.
- TheNewAndy 10y ago> Memory. Defaulting to 16-bits when an 8-bit variable will do can be incredibly wasteful on an 8-bit µC. I hope it doesn't seem like I'm just moving the goalposts, but the obvious answer here is to use a type which is at least 8-bits wide if you only need 8-bits. Such a type exists, and is called "char" (with the appropriate signedness modifiers). This whole discussion has been about writing portable code. Using a "char" here is going to work perfectly on your 8-bit uC. It is also going to work perfectly on your SHARC chip with 32-bit chars. > If you are aware of a machine that provides that, yes! Otherwise, I'd suggest uint32_t. I think we are talking about different things. I am talking about writing portable code. You are talking about writing code that only works on a particular machine (or the class of machines that have a 29-bit int). I know there is a place for that code, and once you are writing code where you actually need to make use of knowledge about the machine, then I'm all for using types that make this clear. However, typically this code only lives at the edges of the system, and the actual "computation" can be written in portable, efficient, readable code without a great deal of trouble. I'm completely aware that long can be 64-bits on some machines. If you care so much about the wasted memory, then uint_least32_t should make you happy - if you are happy with the C99 dependency (which can limit portability, though things are getting better), then I don't see how you can see this as being worse than the fixed width uint32_t. Personally, I have found that while it sounds nice in theory, the domains I've been working in have meant that the memory doesn't make much difference (if it is just sitting on the stack or being passed between functions, then on 64-bit machines, there is typically no differences, as calling conventions tend to pad things out). It is only when you have an array of these in memory that it might start to matter, and here it tends not to matter a great deal - you are typically now optimizing an algorithm for an amd64 machine (read - it has plenty of memory) and the algorithm typically doesn't actually use a lot of it (since it needs to run on tiny micros too). Anyway, I think we probably agree for code which isn't supposed to be portable. Potentially just that I tend to work more on the code that is supposed to be portable, and you work on the code at the edges?
- CamperBob2 10y agoYou have an int16_t? How do you port it to a machine without a 16-bit type? Hint: you're being done a favor by being forced to think about that.
- TheNewAndy 10y agoOk, now lets suppose you have done the job of thinking about it, and you are now up to actually acting on your thinking. What do you write? You want: - A single code base - The code be compiled on the "I don't have a 16-bit type" machine, as well as on your amd64 machine and x86 machine. Having it work on a 24-bit machine might be nice too if you want bonus points. - You don't want to waste memory on the crappy embedded chips where memory is scarce As far as I'm aware, you can either: 1) Use fixed 32-bit types everywhere (you lose both 24-bit compatibility and waste memory... possibly not the end of the world) 2) Use non-fixed width types and tick all the boxes. You don't really gain anything by using the 32-bit types (who cares if you know exactly how big it is - you only needed 16-bits anyway), but you do lose things.
- CamperBob2 10y agoHonestly, there's no way I'd use an MCU that doesn't support 16-bit integer types. I've never even heard of such a thing. It's really a property of the compiler, not the hardware. I use 32-bit ints and floats all the time on 8-bit 8051s and AVRs, both of which are among the most primitive microcontrollers in use today. They are typedefed to S32 and F32 precisely because I want a unified code base that compiles and works the same under Windows. If I know I'll never need more than 8 or 16 bits, I'll use an S8 or S16, but I certainly won't hesitate to use an S32 or even an S64 on these chips if needed. Using "int" is asking for some serious grief in the embedded world, which is why it's almost never done anymore. I've also never heard of a 24-bit processor -- unless you're thinking of some antediluvian bit-slice processor, or something like that? -- but I'm sure the same code will compile and run on it just fine, if and when I ever encounter one. If not, that's not my problem. It can be blamed jointly on the administration and faculty at the school that handed a diploma to the programmer who wrote the C compiler for it. At the end of the day, if I'm that worried about RAM and resource usage, or if I'm building 100,000,000 of something that has to use the smallest, cheapest controller possible, I'm probably writing in assembly anyway.