12

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:

  1. By adding a directive that would instruct the compiler to force certain "fundamental" integer types to be certain sizes.

  2. 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.

  3. 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 an int should yield a result of type wrap32 regardless of whether int is larger than 16 bits, while adding a wrap32 directly to a wrap16 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:

  1. 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 be uint32_t on compilers where a uint32_t would not get promoted, and figure that it's better for code which requires wrap32_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.

  2. 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.

  • 4
    @RobertHarvey Are you sure? As I understand integer promotion, if int is larger than uint16_t, the operands of the multiplication would be promoted to int and the multiplication would be carried out as int multiplication, and the resulting int value would be converted to int64_t for the initialization of who_knows. – user7043 Nov 2 '14 at 22:47
  • 3
    @RobertHarvey How? In OP's code, there is no mention of int , yet it still sneaks in. (Again assuming my understanding of the C standard is correct.) – user7043 Nov 2 '14 at 22:51
  • 2
    @RobertHarvey Sure it sounds bad, but unless you can point out such a way, you're not contributing anything by saying "nah you must be doing something wrong". The very question is how to avoid the integer promotion, or get around its effects! – user7043 Nov 2 '14 at 22:53
  • 3
    @RobertHarvey: One of the historical goals of the C Standards Committee has been to make it possible for almost any machine to have a "C compiler", and have the rules be specific enough that independently-developed C compilers for any particular target machine would be mostly interchangeable. This was complicated by the fact that people started writing C compilers for many machines before the standards were drafted, and the Standards Committee did not want to forbid compilers from doing anything that existing code might rely upon. Some rather fundamental aspects of the standard... – supercat Nov 3 '14 at 17:32
  • 3
    ...are as they are not because anybody tried to formulate a set of rules that "made sense", but rather because the Committee was trying to nail down all the things that the independently-written compilers that already existed had in common. Unfortunately, this approach has led to standards which are simultaneously too vague to allow programmers to specify what needs to be done, but too specific to allow compilers to "just do it". – supercat Nov 3 '14 at 17:38
4

As the typical intention of code like this

uint16_t ffff16 = 0xFFFF;
int64_t who_knows = ffff16 * ffff16;

is to perform the multiplication in 64 bits (the size of the variable the result gets stored in), the usual way to get the (platform independent) correct result is to cast one of the operands to force a 64-bit multiplication:

uint16_t ffff16 = 0xFFFF;
int64_t i_know = (int64_t)ffff16 * ffff16;

I have never encountered any C extensions that make this process automatic.

  • 1
    My question was not how to force the correct evaluation of one particular arithmetic expression (depending upon which kind of result one wants, either cast an operand to uint32_t or else use a macro defined as either #define UMUL1616to16(x,y)((uint16_t)((uint16_t)(x)*(uint16_t)(y))) or #define UMUL1616to16(x,y)((uint16_t)((uint32_t)(x)*(uint16_t)(y))) depending upon the size of int) but rather whether there are any emerging standards for how to handle such things usefully rather than defining my own macros. – supercat Nov 3 '14 at 18:38
  • I should have also mentioned, that for things like hashing and checksum calculations, the purpose will often be to take a result and truncate it to the size of the operands. The typical intention of an expression like (ushort1*ushort2) & 65535u would be to perform mod-65536 arithmetic for all operand values. Reading the C89 rationale, I think it's pretty clear that while the authors recognized that such code might fail on some implementations if the result exceeded 2147483647, they expected such implementations to become increasingly rare. Such code sometimes fails on modern gcc, however. – supercat Mar 6 '17 at 17:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.