4 ms·
I think the prevailing philosophy for C (and later C++) was that code should map closely to the hardware and not expand to complicated "microcode". A left shift
by simias 4y ago
I think the prevailing philosophy for C (and later C++) was that code should map closely to the hardware and not expand to complicated "microcode". A left shift in Cshould just be a left shift in the underlying ISA, within reason. That's where most of the undefined behaviours come from. Having to add masking and other operations to emulate a 16bit shift on a 32bit architecture feels un-C-like, for better or worse.
IMO the real issue is not so much the fact that all shifts of any type < int is treated as if it were an int, it's that the language doesn't force you to acknowledge that in the code. If you got a compilation error when trying to shift a short and had to explicitly promote to int in order to make it through, at the very least it can't lead to an oversight from a careless programmer.
C is trying to be clever but only goes half way, resulting in the worst of both worlds IMO.
- codeflo 4y agoThe ISA might force someone to extend a value to 32 bits (debatable, but let’s go with it). It never forces you to treat an unsigned int as signed. It also doesn’t require inserting UB into the process.
- simias 4y agoI agree, the whole signed vs. unsigned generally feels like an afterthought in C (probably because, to a certain extent, it was). `char`'s sign being implementation-defined is a pretty wild design choice that wasted many hours of my life while porting code between ARM and x86. UBs are not required, but you need them if you want C to behave as a macro-assembler as well as allowing for aggressive optimizations. For instance `a << b` if b is greater than a's width is genuinely UB if you write portable code, different CPUs will do different things in this situation. Defining the behaviour means that the compiler would have to insert additional opcodes to make the behaviour identical on all platforms. You may argue that it's still better than having UB but that's just not C's design philosophy, for better or worse.
- codeflo 4y agoThat’s true, I’d add something more. Your reasoning about hardware differences would only justify implementation-defined behavior, not undefined behavior. The distinction is important here: undefined behavior is when the compiler can make surprising optimizations in other parts of the code assuming something doesn’t happen.
- jcranmer 4y agoI imagine the reason oversized shifts are UB is because some 40-year-old computer hardware trapped on oversized shifts, as traps are always UB in C.
- gsliepen 4y agoIt's worse than that. `char`'s signedness being implementation defined is one thing, but then having the standard library provide a function called `getchar()` that returns not a `char` but an `unsigned char` cast to an `int` is diabolical.
- tsimionescu 4y ago> For instance `a << b` if b is greater than a's width is genuinely UB if you write portable code, different CPUs will do different things in this situation. Defining the behaviour means that the compiler would have to insert additional opcodes to make the behaviour identical on all platforms. Your seem to be mixing up implementation defined behavior and undefined behavior. It would have been perfectly reasonable to make this choice if signed integer ovwrflow were implementation-defined, but it is unfortunately not - it is undefined behavior instead. This means that a program containing this instruction is not valid C and may "legally" have any effect whatsoever.