views:

91

answers:

4

Consider the following piece of C code:

#include <stdint.h>

uint32_t inc(uint16_t x) {
 return x+1;
}

When compiled with gcc-4.4.3 with flags -std=c99 -march=core2 -msse4.1 -O2 -pipe -Wall on a pure x86_64 system, it produces

movzwl %di,%eax
inc    %eax
retq

Now, unsigned overflow is predicted in C. I do not know much about x86_64 assembly, but as far as I can see the 16bit argument register is being moved to a 32bit register, which is incremented and returned. My question is, what if x == UINT16_MAX. An overflow would occur and the standard dictates x+1==0, right? However, given %eax is a 32bit register, it now contains UINT16_MAX+1, which is not correct.

This lets me connect one question here: is there a portable way to disable unsigned overflow in C so that the compiler can assume the upper bits of a small variable stored in a large register will always be 0 (so it needs not clear them)? If not (or if the solution is syntactically nasty), is there a way to do it at least in GCC?

Thank you very much for your time.

+5  A: 

No, C types are subject to default promotions. Assuming uint16_t has lower conversion rank than int, it will be promoted to int and the addition will be carried out as an int, then converted to uint32_t when returned.

As for your related question at the end, I don't quite follow what you want.

R..
@R.: isn't it that it is promoted to `unsigned int`?
Jens Gustedt
@Jens: no. The range of `uint16_t` fits in `int`, so it will be promoted to `int`. Same for `unsigned char`.
R..
Sorry, I did not take that into account. Still, when I change the code to "return x + (uint16_t)1" it gives the same result. I believe the promotion rule states that the sum of two uint16_t give an uint16_t, or do they give an uint32_t?
Luís Fernando Schultz Xavier
@R: ah, right, since `int` has to be at least 32 bit wide.
Jens Gustedt
@Luis: no, there is simply **no such thing** as smaller-than-`int` arithmetic in C. The result of **any** arithmetic expression has type `int` or larger. The sum of two `uint16_t` variables has type `int` if `int` is higher conversion rank than `uint16_t`, and `uint16_t` if `int` is lower conversion rank than `uint16_t`. Which one happens depends on the particular rank of `uint16_t` on your implementation, but there are rules relating the relative sizes of the ranges of types to their ranks.
R..
@R: Well, for me it is clear the standard is not very coherent, let alone simple. :-) That aside, in x86_64 with gcc, sizeof(int)==4 but sizeof(size_t)==sizeof(void*)==8. Thus, the question can be extended to the types uint64_t and uint32_t, which both have greater than or equal to rank than int. This way we get around that, again in my opinion, strange restriction.
Luís Fernando Schultz Xavier
You could use those types on plain 32-bit x86 too, and the result should be the same. It's simply using a type that's **too small** that's your problem.
R..
A: 

A peculiarity of the way the standard describes integer overflow is that it allows compilers to assume that an overflow cannot occur. In the case you show there, the compiler is not expected to preserve the behavior of an overflow, since after all, the range of possible values that x+1 may take (assuming that overflow doesn't exist) fit in the return type.

TokenMacGuy
True but irrelevant. This expression cannot overflow.
R..
+2  A: 

Use a coding style that does not use compiler intermediaries for calculations, note that (1) is going to have the data type int.

uint32_t inc(uint16_t x) {
 uint16_t y = x + 1;
 return y;
}
Steve-o
That gives the correct behaviour. Thanks. Still, what I want is an idea on how to let the compiler assume there will be no overflow of this kind so it doesn't need to erase that extra bit in the register representing y.
Luís Fernando Schultz Xavier
You'll have to use a different language, it is really a rather fundamental artefact of the C language. Examples of use include Galois Field arithmetic for digital signal processing.
Steve-o
A: 

For your second question, in C there is no such thing as overflow for unsigned types, the applicable term is wrapping. By definition unsigned types are computed modulo 2^width. Whenever you cast a wider unsigned type to one that is narrower the upper bits will simply be thrown away. All C compilers should implement it like this, there is nothing you have to worry about.

In essence unsigned types are quite simple, the nasty things only come for signed types.

Jens Gustedt
True but irrelevant. This code contains no overflows, signed or unsigned.
R..
It is actually relevant. The compiler does it for me, but it costs runtime performance. I want to disable it.
Luís Fernando Schultz Xavier
@R.: sure the code doesn't contain it. I just answered the question, so it is relevant to that second question.
Jens Gustedt
somebody giving me -1 for such a thing, wow.
Jens Gustedt
@Luís Fernando Schultz Xavier: I don't think that it has an influence on performance. Just always use the correct unsigned types and all what appears complicated masking or so to you will just result in simple assembler instructions. If you are really worried, look at the assembler that this produces, and if you are even more worried benchmark it.
Jens Gustedt
@Jens Gustedt: That is pretty much what I am trying to avoid. Generated assembly code changes with compiler versions, platform and a bunch of other factors. I am looking for a way to change the semantics of the operations on unsigned types.
Luís Fernando Schultz Xavier