Sterpu Mihai
Sterpu Mihai

Reputation: 618

integer promotion isn't taken into account

Let's take the following code which we compile for a 32 bit system:

typedef unsigned int uint32;
typedef unsigned char uint8;
...
uint8 y = 0xFF;
uint32 x = (y << 24U);

y can be promoted to int as, and I quote from the C Standard:

If an int can represent all values of the original type, the value is converted to an int

The shifting value is valid as, according to the same standard:

The integer promotions are performed on each of the operands. The type of the result is that of the promoted left operand. If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined.

So because y is promoted to an int and the shift value is less than the width of the int (24 < 32), there is no undefined behaviour.

However, Misra has a rule, 12.2, where it states that:

The right hand operand of a shift operator shall lie in the range zero to one less than the width in bits of the essential type of the left hand operand

Note the essential keyword in the rule text. This means that Misra only looks at the essential type of y, which is uint8 and then sees that 24>=8 (width of uint8) thus it treats it as a violation.

Why this discrepancy? If the C compiler allows promotion to be taken into consideration, why isn't Misra? Any C compiler performs the promotion of the left operand thus there can't be a compiler that messes up the shifting due an undefined behaviour.

Upvotes: 6

Views: 121

Answers (3)

Lundin
Lundin

Reputation: 214770

y can be promoted to int as, and I quote from the C standard

That is correct, it is promoted to an int as per integer promotion before shift (see Implicit type promotion rules). MISRA C also explains this briefly in the introductory part of chapter 10 of MISRA C:2023.

What might be the root of your confusion is the peculiar part with shift operators specifically. They do not apply the "usual arithmetic conversions" like most binary operators. For example in case of y + 24U then y would be balanced to unsigned int before addition. But in case of shift, "the type of the result is that of the promoted left operand" as said in the part you quote. Therefore the type of the right operand is irrelevant.

The shifting value is valid as, according to the same standard

No. See C23 6.5.8, emphasis mine:

The integer promotions are performed on each of the operands. /--/

(Now the left operand is int in your case.)

The result of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are filled with zeros. If E1 has an unsigned type, the value of the result is E1 × 2E2, wrapped around. If E1 has a signed type and nonnegative value, and E1 × 2E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.

Specifically 0xFF * 2^24 = 4,278,190,080. The range of int only goes up to 2^31-1 = 2,147,483,647.

Note the essential keyword in the rule text. This means that Misra only looks at the essential type of y, which is uint8 and then sees that 24>=8(width of uint8) thus it treats it as a violation.

Yes and that's a sound rule since it prevents undefined behavior in this case.

General best practice: When the left operand of a shift is a small integer type, then before shifting always cast the left operand to either unsigned int or the equivalent stdint.h type (either uint16_t or uint32_t depending on system).

Upvotes: 2

user2357112
user2357112

Reputation: 281748

The MISRA rule is intended to protect against variations in platform integer size. On a typical system with 32-bit ints, it's possible to shift a small enough unsigned char left 24 bits without overflow (though your particular example still exhibits signed overflow). But such platforms are not the only platforms MISRA C is concerned with.

Some platforms have 16-bit ints. On a platform with 16-bit int and 8-bit char, trying to shift an unsigned char left 24 bits would automatically be undefined behavior.

It would be possible for MISRA to specify a more lenient rule that still protects against platform variation, by accounting for the standard-mandated minimum width of an int, which is at least 16 bits. But the goal of MISRA C is better served by choosing consistency over leniency.

Your particular example is undefined behavior even with 32-bit ints. y gets promoted to a signed int, not unsigned, and the value of y is big enough that the shift produces signed overflow.

Upvotes: 5

Nate Eldredge
Nate Eldredge

Reputation: 58673

C17 6.5.7p4:

If E1 has a signed type and nonnegative value, and E1 × 2^E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.

The result type is int, which (assuming typical 32 bit types) cannot represent 255 × 2^24 = 4278190080 as it is greater than 2^31-1 = 2147483647. Thus the behavior is undefined.

Upvotes: 3

Related Questions