Pregunta

I'm a little confused about how the C language treats different sized ints when you do basic arithmetic and bitwise operation. what would happen if in this case:

int8_t even = 0xBB;
int16_t twice = even << 8;
twice = twice + even; 

What If i went the other way and attempted to add a 16 bit int to an 8 bit? is the normal int declaration dynamic? Why would i want to designate a size? What happens when I add to 8 bit ints that are both 0xFF?

¿Fue útil?

Solución

As I mentioned in my comment, everything gets promoted to at least an int during arithmetic.

Here's an example program to demonstrate that:

main(){
    struct {
        char x : 1;
    }t;
    t.x = 1;
    int i = t.x << (sizeof i * 8 - 1);
    printf("i = %x\n",i);
}

t.x is only one bit, but in this operation it is promoted all the way to an integer to give the output:

i = 80000000

On the other hand, if we add the line

long long j = t.x << (sizeof j * 8 - 1)

gcc gave me the warning:

warning: left shift count >= width of type [enabled by default]

What If i went the other way and attempted to add a 16 bit int to an 8 bit?

All the arithmetic would be done at integer precision (probably 32 bits) and then the bottom 8 bits of the result would be stored in the 8 bit number.

Is the normal int declaration dynamic?

No. Its a fixed width on an implementation (probably 32 bits on yours).

Why would i want to designate a size?

Maybe you have space constraints. Maybe your algorithm was made to work with a specific size integer. Maybe you really want to work on some modulo field provided by uint16_t (Z_65536).

What happens when I add to 8 bit ints that are both 0xFF?

The promotion doesn't matter here, the 8 bit result will be 0xFE. If you were to store the result in a 16 bit number, then the result will be 0x1FE (Unless int's in your implementation are only 8 bits. This is highly unlikely except for some esoteric embedded applications).

Edit

I wrote about the unsigned convention here, because you representing the number as 0xFF seemed to refer to unsigned numbers (in K&R C 0xFF is actually an unsigned literal). If you were actually referring to the signed 8-bit value 0xFF that is equivalent to -1, and your problem becomes sort of trivial. No matter how big the integer, it should always be able to represent -1, as well as -1 + (-1) = -2 (you only actually need two bits to represent these numbers).

Otros consejos

Normal "int" is not dynamic, but is not common across compilers; it's the size that is most convenient for the CPU you're compiling for. You would want to designate a size if you have some reason (network protocol, file format, etc) why you actually care about the representation.

Answers to your exercises below the answer to your question:


Your answer can be found in the C11 final draft spec (was ratified in October 2011, but the "final" document is not free): http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf

The first relevant section is 6.3.1.1 Conversions : Arithmetic Operands : Boolean, characters and integers, beginning on page 50

If an int can represent all values of the original type (as restricted by the width, for a bit-field), the value is converted to an int; otherwise, it is converted to an unsigned int. These are called the integer promotions. All other types are unchanged by the integer promotions.

The integer promotions preserve value including sign. As discussed earlier, whether a "plain" char is treated as signed is implementation-defined.

Essentially, the integer types are all promoted to at least int, and possibly to larger sizes than that if any of the operands had a rank greater than that. They generally become signed, but remain unsigned if either operand's type has greater range than int.

There's a bunch of stuff specifically defining how the conversions are performed, and codifying the rules of which types are "higher rank" (bigger) than others, and then we reach the real meat in 6.3.1.8: Usual arithmetic conversions:

Many operators that expect operands of arithmetic type cause conversions and yield result types in a similar way. The purpose is to determine a common real type for the operands and result. For the specified operands, each operand is converted, without change of type domain, to a type whose corresponding real type is the common real type. Unless explicitly stated otherwise, the common real type is also the corresponding real type of the result, whose type domain is the type domain of the operands if they are the same, and complex otherwise. This pattern is called the usual arithmetic conversions:

... some stuff about floats ...

Otherwise, the integer promotions are performed on both operands. Then the following rules are applied to the promoted operands:

If both operands have the same type, then no further conversion is needed. Otherwise, if both operands have signed integer types or both have unsigned integer types, the operand with the type of lesser integer conversion rank is converted to the type of the operand with greater rank. Otherwise, if the operand that has unsigned integer type has rank greater or equal to the rank of the type of the other operand, then the operand with signed integer type is converted to the type of the operand with unsigned integer type.

Otherwise, if the type of the operand with signed integer type can represent all of the values of the type of the operand with unsigned integer type, then the operand with unsigned integer type is converted to the type of the operand with signed integer type.

Otherwise, both operands are converted to the unsigned integer type corresponding to the type of the operand with signed integer type.


int8_t even = 0xBB;

0xBB formed as a constant of type int (0x000000BB), then truncated down to int8_t

int16_t twice = even << 8;

the value of even is sign extended from int8_t to int, becoming 0xFFFFFFBB (if I assume int is 32-bits on this machine) The value of 8 is formed as type int These types match, so no further conversions occur The operator << is performed (now that types match), yielding 0xFFFFBB00 This value is truncated to fit in int16_t, yielding 0xBB00

twice = twice + even;

The value of twice is sign extended from int16_t to int, yielding 0xFFFFBB00 (-17664) The value of even is sign extended from int8_t to int, yielding 0xFFFFFFBB (-69) These values are added, yielding 0xFFFFBABB (-17733) This value is then truncated to fit in int16_t, yielding 0xBABB

What happens when I add to 8 bit ints that are both 0xFF?

They are both sign extended to int yielding 0xFFFFFFFF, then added yielding 0xFFFFFFFE. This may or may not be truncated back down depending on where you store it.


The compiler is of course free to notice that much of this extending/truncating stuff is worthless busywork because the output bits are not kept, and optimize it out. But the result is required to be "as if" if had done things the long way.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top