An interesting characteristic of C compared with some other languages is that many of its data types are based upon the word size of the target architecture, rather than being specified in absolute terms. While this allows the language to be used to write code on machines that might have difficulty with certain types, it makes it very difficult to design code which will run consistently on different architectures. Consider the code:
uint16_t ffff16 = 0xFFFF;
int64_t who_knows = ffff16 * ffff16;
On an architecture where int
is 16 bits (still true of many small microcontrollers) this code would assign a value of 1 using well-defined behavior. On machines where int
is 64 bits, it would assign a value 4294836225, again using well-defined behavior. On machines where int
is 32 bits, it would likely assign a value of -131071 (I don't know if that would be Implementation-Defined or Undefined Behavior). Even though the code uses nothing except what are nominally supposed to be "fixed-sized" types, the standard would require that two different kinds of compiler in use today would yield two different results, and many popular compilers today would yield a third.
This particular example is somewhat contrived, in that I would not expect in real-world code to assign the product of two 16-bit values directly to a 64-bit value, but it is was chosen as a brief example to show three ways integer promotions may interact with supposedly-fixed-sized unsigned types. There are some real-world situations where it's necessary for math on unsigned types to be performed according to the rules of mathematical integer arithmetic, others where it's necessary that it be performed according to the rules of modular arithmetic, and some where it really doesn't matter. A lot of real-world code for things like checksums relies upon uint32_t
arithmetic wrapping mod 2³², and upon being able to perform arbitrary uint16_t
arithmetic and get results which are, are at minimum, defined as being accurate mod 65536 (as opposed to triggering Undefined Behavior).
Even though this situation would clearly seem undesirable (and will become more so as 64- bit processing becomes the norm for many purposes), the C standards committee from what I've observed prefers to introduce language features which are already used in some notable production environments, rather than inventing them "from scratch". Are there any notable extensions to the C language which would allow code to specify not just how a type will be stored but also how it should behave in scenarios involving possible promotions? I can see at least three ways a compiler extension might resolve such issues:
By adding a directive that would instruct the compiler to force certain "fundamental" integer types to be certain sizes.
By adding a directive that would instruct the compiler to evaluate various promotion scenarios as though the machine's types had particular sizes, regardless of the actual sizes of the types on the target architecture.
By allowing means of declaring types with specific characteristics (e.g. declare that a type should behave as a mod-65536 wrapping algebraic ring, regardless of the underlying word size, and should not be implicitly convertible to other types; adding a
wrap32
to anint
should yield a result of typewrap32
regardless of whetherint
is larger than 16 bits, while adding awrap32
directly to awrap16
should be illegal (since neither could convert to the other).
My own preference would be the third alternative, since it would allow even machines with unusual word sizes to work with a lot of code that expects variables to "wrap" as they would with power-of-two sizes; the compiler may have to add bit-masking instructions to make the type behave suitably, but if code needs a type that wraps mod 65536, it's better to have the compiler generate such masking on machines that need it than to clutter the source code with it or simply have such code by unusable on machines where such masking would be needed. I'm curious, though, whether there are any common extensions which would achieve portable behavior via any of the above means, or via some means I haven't thought of.
To clarify what I'm looking for, there are a few things; most notably:
While there are many ways by which code could be written so as to ensure desired semantics (e.g. defining macros to perform do math on particular-sized unsigned operands so as to yield a result which explicitly either wraps or does not) or at least prevent undesired semantics (e.g. conditionally-define a type
wrap32_t
to beuint32_t
on compilers where auint32_t
would not get promoted, and figure that it's better for code which requireswrap32_t
to fail compilation on machines where that type would get promoted than to have it run and yield bogus behavior), if there is any way of writing the code which would play most favorably with future language extensions, using that would be better than devising my own approach.I have some pretty solid ideas for how the language could be extended so as to resolve many integer-size issues, allowing code to yield identical semantics on machines with different word sizes, but before I spend any significant time writing them up I'd like to know what efforts in that direction have already been undertaken.
I do not in any way wish to be seen as disparaging the C Standards Committee or the work they have produced; I expect, however, that within a few years it will become necessary to make code work correctly on machines where the "natural" promotion type would 32 bits, as well as those where it would be 64 bits. I think with some modest extensions to the language (more modest than many of the other changes between C99 nnd C14) it would be possible to not only provide a clean way of efficiently using 64-bit architectures, but in the bargain also facilitate interaction with the "unusual-word-size" machines that the standard has historically bent over backward to support [e.g. making it possible for a machines with a 12-bit char
to run code that expects an uint32_t
to wrap mod 2³²]. Depending upon the direction future extensions take, I would also expect it should be possible to define macros which would allow code written today to be usable on today's compilers where the default integer types behave as "expected", but also usable on future compilers where integer types would be default behave differently, but where can provide the required behaviors.
int
is larger thanuint16_t
, the operands of the multiplication would be promoted toint
and the multiplication would be carried out asint
multiplication, and the resultingint
value would be converted toint64_t
for the initialization ofwho_knows
.int
, yet it still sneaks in. (Again assuming my understanding of the C standard is correct.)