The best known example is that of the 80-bit floats of the 8087 FPU, but that’s relatively rare compared to loading integers into larger registers.
For example, if you compile
if( foo + 10 > bar) goto baz
to assembly similar to move foo to R1
move bar to R2
add 10 to R1
subtract R2 from R1
branch-if-greater-than-zero baz
and foo and bar are 32-bits, that add can overflow if R1 and R2 also are 32-bits, but never overflows if R1 and R2 are 64-bit. That changes the result of the comparison if, for example foo = INT_MAX
bar = INT_MAX
The designers of the CPU with 64-bits registers may not want to add variants of add (and subtract, mult, etc) that work on 32 bits (and 16 bits, and 8 bits).They also won’t want to have their C compiler slowing down this kind of code, only because other (often relatively old and slow) CPUs exist.
Even if that’s true (I don’t know enough of x86 assembly to judge that, but one thing I can think of that it may require extra register-to-register moves), that it isn’t an issue for one architecture won’t help a committee discussing the C standard.
Also, others in this thread describe what you call “let me annotate” as “forking C”.
In any case, the problem doesn't really apply to x86 because all x86 compilers I'm aware of use the "expected" size for the various types rather than larger-than-needed-for-speed, exactly because the smaller operations are all generally available.
Can you elaborate on that point? Why was it necessary then, but not still necessary now?
I would like to give an example of how supporting both 1s- and 2s-compliment is the source of a specific UB, but I can't take the time to do that right now, regrettably. Similarly, supporting both Big Endian and Little Endian was necessary. As was supporting ASCII, EBCDIC, and probably Fieldata and five level Baudot (looking at you, trigraphs). All of this generality made it hard to say anything useful in some areas, so you end up in some cases just calling it Undefined, since there was no consensus on how it should be defined.
For example, forcing extra code to be inserted, that is, a check (or even a call out to a library), so one unified semantic is followed, and there is no undefined behaviour. Of course, you wouldn't mandate how the semantics are followed.
The problem is, it was very hard for us to accept this kind of solution when we grew up feeling like every cycle counted, so that's what I blame for a lot of the UB we see today in the standard.
This is a very valid justification of implementation defined. Maybe even unspecified. But undefined? How could you justify that?
Even if some platform does not support something at all, and goes bananas whenever you do it (say, signed overflow that traps), what would stopped the standard to say that whether the stuff is undefined or not is platform dependent?
Perhaps the committee didn't anticipate how unreasonable compiler writers turned out to be?
----
The terms unspecified behavior, undefined behavior, and implementation-defined behavior are used to categorize the result of writing programs whose properties the Standard does not, or cannot, completely describe. The goal of adopting this categorization is to allow a certain variety among implementations which permits quality of implementation to be an active force in the marketplace as well as to allow certain popular extensions, without removing the cachet of conformance to the Standard. Appendix F to the Standard catalogs those behaviors which fall into one of these three categories.
Unspecified behavior gives the implementor some latitude in translating programs. This latitude does not extend as far as failing to translate the program.
Undefined behavior gives the implementor license not to catch certain program errors that are difficult to diagnose. It also identifies areas of possible conforming language extension: the implementor may augment the language by providing a definition of the officially undefined behavior.
Implementation-defined behavior gives an implementor the freedom to choose the appropriate approach, but requires that this choice be explained to the user. Behaviors designated as implementation-defined are generally those in which a user could make meaningful coding decisions based on the implementation definition. Implementors should bear in mind this criterion when deciding how extensive an implementation definition ought to be. As with unspecified behavior, simply failing to translate the source containing the implementation-defined behavior is not an adequate response.