Suppose that on a C implementation (e.g. on a x86 C compiler) USHRT_MAX = 65535
and INT_MAX = 2147483647
. Is, then, the following statement well-defined?
unsigned short product = USHRT_MAX * USHRT_MAX;
According to the following in the C99 standard both operands are promoted to int
(since int
can represent all possible values of unsigned short
) and, therefore, the result is not well-defined, since an overflow will occur (65535 ^ 2 = 4294836225 > 2147483647
), which means that the value of product
is not well-defined:
6.3.1.1-1
If an int can represent all values of the original type, the value is converted to an int; otherwise, it is converted to an unsigned int. These are called the integer promotions.(48) All other types are unchanged by the integer promotions.
48) The integer promotions are applied only: as part of the usual arithmetic conversions, to certain argument expressions, to the operands of the unary +, -, and ~ operators, and to both operands of the shift operators, as specified by their respective subclauses.
However, according to the following, the result is well-defined, since computations involving unsigned operands do not overflow:
6.2.5-9
The range of nonnegative values of a signed integer type is a subrange of the corresponding unsigned integer type, and the representation of the same value in each type is the same.(31) A computation involving unsigned operands can never overflow, because a result that cannot be represented by the resulting unsigned integer type is reduced modulo the number that is one greater than the largest value that can be represented by the resulting type.
Does the variable product
in the aforementioned statement have a well-defined value?
EDIT: What should happen in the following case?
unsigned short lhs = USHRT_MAX;
unsigned short rhs = USHRT_MAX;
unsigned short product = lhs * rhs;
Integer promotion is the implicit conversion of a value of any integer type with rank less or equal to rank of int or of a bit field of type _Bool, int, signed int, unsigned int, to the value of type int or unsigned int.
Introduction. Type promotion in C is a method to convert any variable from one datatype to another. C allows variables of different datatypes to be present in a single expression. There are different types of type conversions available in C. They are Implicit type conversion and Explicit type conversion.
The promotion wins.
Says section 5.2.4.2.1 about the constants USHRT_MAX
etc.:
The values given below shall be replaced by constant expressions suitable for use in
#if
preprocessing directives. Moreover, except forCHAR_BIT
andMB_LEN_MAX
, the following shall be replaced by expressions that have the same type as would an expression that is an object of the corresponding type converted according to the integer promotions.
So the multiplication is on int
s, and involves no unsigned operands, unambiguously, there is no conforming way to implement USHRT_MAX
to get an operation involving unsigned operands if USHRT_MAX < INT_MAX
. Thus you have overflow, and undefined behaviour.
Regarding the added question
EDIT: What should happen in the following case?
unsigned short lhs = USHRT_MAX; unsigned short rhs = USHRT_MAX; unsigned short product = lhs * rhs;
That is exactly the same situation. The operands of *
are subject to the integer promotions, all values of type unsigned short
can be represented as int
s by the assumption on the values of USHRT_MAX
and INT_MAX
, so the multiplication is on int
s, and with the specified values overflows.
You need to convert at least one operand to an unsigned type that is not promoted to int
in oerder to have the multiplication be performed on unsigned operands.
You get UB since by the time the multiplication operator is applied, its operands are already signed integers (because of the promotions to int
occurring first).
You can work-around that with this:
unsigned short product = USHRT_MAX * (unsigned)USHRT_MAX;
Proof that (unsigned)some_integer
stays unsigned:
#include <stdio.h>
int main(void)
{
printf("1u * (-1) = %f\n", (((unsigned)1) * (-1)) + 0.0);
printf("1 * (-1) = %f\n", (1 * (-1)) + 0.0);
return 0;
}
Output (ideone):
1u * (-1) = 4294967295.000000
1 * (-1) = -1.000000
Good catch, btw.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With