Implicit type promotion rules
Asked Answered
F

5

140

This post is meant to be used as a FAQ regarding implicit integer promotion in C, particularly implicit promotion caused by the usual arithmetic conversions and/or the integer promotions.

Example 1)
Why does this give a strange, large integer number and not 255?

unsigned char x = 0;
unsigned char y = 1;
printf("%u\n", x - y); 

Example 2)
Why does this give "-1 is larger than 0"?

unsigned int a = 1;
signed int b = -2;
if(a + b > 0)
  puts("-1 is larger than 0");

Example 3)
Why does changing the type in the above example to short fix the problem?

unsigned short a = 1;
signed short b = -2;
if(a + b > 0)
  puts("-1 is larger than 0"); // will not print

(These examples were intended for a 32 or 64 bit computer with 16 bit short.)

Flagg answered 6/9, 2017 at 10:50 Comment(8)
I suggest documenting the assumptions for the examples, e.g. example 3 assumes that short is narrower than int (or in other words, it assumes that int can represent all the values of unsigned short).Kuebbing
Wait a sec the OP is the same guy who answered the question? It says Lundin asked, best answer is Lundin's as well lolCaylacaylor
@Caylacaylor Yes, the intention is to write a FAQ entry. Sharing knowledge this way is fine for SO - next time you post a question note the checkbox "answer your own question". But of course the question is still treated like any other question and others can post answers too. (And you don't earn any rep from accepting your own answer)Flagg
@savram: It is absolutely fine to share knowledge this way. See here: self answer.Stoll
Neither answer so far mentions the fact that printf("%u\n", x - y); causes undefined behaviourVeery
@Veery I changed it to %d, happy now? Also, I'd rather not drag the odd "default argument promotions" into this post, they are left out on purpose, not to confuse the reader with language rules of peripheral interest.Flagg
@Flagg No, not happy now. The first code snippet after your edit will print -1 on most implementations yet you ask "Why does this give a strange, large integer number and not 255?" This is relevant to the topic because it's a catch-22 of sorts: you can't know what format specifier to use until you understand the promotion rules.Veery
Nice example is ~((u8)(1 << 7)) to the list.Hightension
F
199

C was designed to implicitly and silently change the integer types of the operands used in expressions. There exist several cases where the language forces the compiler to either change the operands to a larger type, or to change their signedness.

The rationale behind this is to prevent accidental overflows during arithmetic, but also to allow operands with different signedness to co-exist in the same expression.

Unfortunately, the rules for implicit type promotion cause much more harm than good, to the point where they might be one of the biggest flaws in the C language. These rules are often not even known by the average C programmer and therefore cause all manner of very subtle bugs.

Typically you see scenarios where the programmer says "just cast to type x and it works" - but they don't know why. Or such bugs manifest themselves as rare, intermittent phenomena striking from within seemingly simple and straight-forward code. Implicit promotion is particularly troublesome in code doing bit manipulations, since most bit-wise operators in C come with poorly-defined behavior when given a signed operand.


Integer types and conversion rank

The integer types in C are char, short, int, long, long long and enum.
_Bool/bool is also treated as an integer type when it comes to type promotions.

All integers have a specified conversion rank. C11 6.3.1.1, emphasis mine on the most important parts:

Every integer type has an integer conversion rank defined as follows:
— No two signed integer types shall have the same rank, even if they have the same representation.
— The rank of a signed integer type shall be greater than the rank of any signed integer type with less precision.
— The rank of long long int shall be greater than the rank of long int, which shall be greater than the rank of int, which shall be greater than the rank of short int, which shall be greater than the rank of signed char.
— The rank of any unsigned integer type shall equal the rank of the corresponding signed integer type, if any.

— The rank of any standard integer type shall be greater than the rank of any extended integer type with the same width.
— The rank of char shall equal the rank of signed char and unsigned char.
— The rank of _Bool shall be less than the rank of all other standard integer types.
— The rank of any enumerated type shall equal the rank of the compatible integer type (see 6.7.2.2).

The types from stdint.h sort in here too, with the same rank as whatever type they happen to correspond to on the given system. For example, int32_t has the same rank as int on a 32 bit system.

Further, C11 6.3.1.1 specifies which types are regarded as the small integer types (not a formal term):

The following may be used in an expression wherever an int or unsigned int may be used:

— An object or expression with an integer type (other than int or unsigned int) whose integer conversion rank is less than or equal to the rank of int and unsigned int.

What this somewhat cryptic text means in practice, is that _Bool, char and short (and also int8_t, uint8_t etc) are the "small integer types". These are treated in special ways and subject to implicit promotion, as explained below.


The integer promotions

Whenever a small integer type is used in an expression, it is implicitly converted to int which is always signed. This is known as the integer promotions or the integer promotion rule.

Formally, the rule says (C11 6.3.1.1):

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.

This means that all small integer types, no matter signedness, get implicitly converted to (signed) int when used in most expressions.

This text is often misunderstood as: "all small signed integer types are converted to signed int and all small, unsigned integer types are converted to unsigned int". This is incorrect. The unsigned part here only means that if we have for example an unsigned short operand, and int happens to have the same size as short on the given system, then the unsigned short operand is converted to unsigned int. As in, nothing of note really happens. But in case short is a smaller type than int, it is always converted to (signed) int, regardless of it the short was signed or unsigned!

The harsh reality caused by the integer promotions means that almost no operation in C can be carried out on small types like char or short. Operations are always carried out on int or larger types.

This might sound like nonsense, but luckily the compiler is allowed to optimize the code. For example, an expression containing two unsigned char operands would get the operands promoted to int and the operation carried out as int. But the compiler is allowed to optimize the expression to actually get carried out as an 8-bit operation, as would be expected. However, here comes the problem: the compiler is not allowed to optimize out the implicit change of signedness caused by the integer promotion because there is no way for the compiler to tell if the programmer is purposely relying on implicit promotion to happen, or if it is unintentional.

This is why example 1 in the question fails. Both unsigned char operands are promoted to type int, the operation is carried out on type int, and the result of x - y is of type int. Meaning that we get -1 instead of 255 which might have been expected. The compiler may generate machine code that executes the code with 8 bit instructions instead of int, but it may not optimize out the change of signedness. Meaning that we end up with a negative result, that in turn results in a weird number when printf("%u is invoked. Example 1 could be fixed by casting the result of the operation back to type unsigned char.

With the exception of a few special cases like ++ and sizeof operators, the integer promotions apply to almost all operations in C, no matter if unary, binary (or ternary) operators are used.


The usual arithmetic conversions

Whenever a binary operation (an operation with 2 operands) is done in C, both operands of the operator have to be of the same type. Therefore, in case the operands are of different types, C enforces an implicit conversion of one operand to the type of the other operand. The rules for how this is done are named the usual arithmetic conversions (sometimes informally referred to as "balancing"). These are specified in C11 6.3.18:

(Think of this rule as a long, nested if-else if statement and it might be easier to read :) )

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:

  • First, if the corresponding real type of either operand is long double, the other operand is converted, without change of type domain, to a type whose corresponding real type is long double.

  • Otherwise, if the corresponding real type of either operand is double, the other operand is converted, without change of type domain, to a type whose corresponding real type is double.

  • Otherwise, if the corresponding real type of either operand is float, the other operand is converted, without change of type domain, to a type whose corresponding real type is float.

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

Notable here is that the usual arithmetic conversions apply to both floating point and integer variables. In the case of integers, we can also note that the integer promotions are invoked from within the usual arithmetic conversions. And after that, when both operands have at least the rank of int, the operators are balanced to the same type, with the same signedness.

This is the reason why a + b in example 2 gives a strange result. Both operands are integers and they are at least of rank int, so the integer promotions do not apply. The operands are not of the same type - a is unsigned int and b is signed int. Therefore the operator b is temporarily converted to type unsigned int. During this conversion, it loses the sign information and ends up as a large value.

The reason why changing type to short in example 3 fixes the problem, is because short is a small integer type. Meaning that both operands are integer promoted to type int which is signed. After integer promotion, both operands have the same type (int), no further conversion is needed. And then the operation can be carried out on a signed type as expected.

Of note, C++ applies pretty much identical rules.

Flagg answered 6/9, 2017 at 10:50 Comment(20)
"Whenever a small integer type is used in an expression, it is implicitly converted to int which is always signed." Could you point to the exact place in the standard that says that it should happen? The C11 6.3.1.1 quote says how it happens (if it happens) but it doesn't say that it must happen e.g., why x - y in the question behaves as (unsigned)(int)((int)x - (int)y) instead of (unsigned)(int)((Uchar)((Uchar)x - (Uchar)y)) goo.gl/nCvJy5 . Where does the standard says that if x is char then +x is int (or unsigned)? In c++ it is §5.3.1.7 goo.gl/FkEakXRuperto
@Ruperto For binary operators where the usual arithmetic conversions apply, the "should" part is included as part of those rules, quoted above. For other operators, they are promoted if the specific operator (chap 6.5) requires it. Take for example the unary - operator, 6.5.3.3: "The result of the unary - operator is the negative of its (promoted) operand. The integer promotions are performed on the operand, and the result has the promoted type.". Another example (special case) is the prefix ++, which does not mention integer promotion, and therefore its operand is not promoted.Flagg
@Flagg «the "should" part is included as part of those rules» I don't see it. Could you provide a specific quote from the standard that says that operands of the same small integer type are promoted while performing a binary operation such as x-y? (in other words that there is no (char)-(char) operator only (int)-(int) or wider — It is true as far as I know). c11 6.5.6/4 points to 6.3.1.8/1 which says "the purpose is to determine common real type" but if operands are already of the same type, it is not explicit that any conversation should be performed.Ruperto
@Ruperto "Otherwise, ..." (if neither operand is float type) "...the integer promotions are performed on both operands." . And then afterwards "If both operands have the same type, then no further conversion is needed."Flagg
@Flagg yes, I've seen it and I interpret it the same way but I hoped there is something more explicit: it is clear that if the usual arithmetic conversions are performed then the operands such as x,y are promoted but it is not very explicit whether the conversions must be performed if the operands have the same type already.Ruperto
@Ruperto It is perfectly clear to me. 6.5.6/4 explicitly says, black on white in normative text, that the usual arithmetic conversions have to be performed: "If both operands have arithmetic type, the usual arithmetic conversions are performed on them."Flagg
@Flagg yes, I explicitly mentioned it above. The unclear part: "the purpose is to determine the common real type" i.e., if the operands already have the same type then it might be interpreted that the conversions are a no-op — I know that it is not actually so e.g., (char)+(char) is always promoted to (int)+(int) (or unsigned).Ruperto
"Example 1 could be fixed by casting one or both operands to type unsigned int." The suggested cast(s) won't yield 255 as the OP expected. The proper fix is to cast the result of the subtraction back to the (unsigned char) that the operands started from, as in (unsigned char) (x-y): this will give the OP the expected 255. People often fail to appreciate casting to a smaller size, however, this is the proper way to accomplish truncation (which will be followed by implicit/automatic signed or zero extension to ~int size).Cruiser
@ErikEidt Yes it would, as you get unsigned int wrap-around and then an implicit conversion back to unsigned char. The danger with casting after the operation is that the bug could already have struck. Suppose for example that you have x << y instead. Undefined behavior of shifting beyond the size of x could occur and then no cast afterwards will save the program. The pedantic version (MISRA-C compliance etc) will cast both before the operation and afterwards, so that no implicit conversions occur at all.Flagg
1) The standard quote says "The rank of any standard integer type shall be greater than the rank of any extended integer type with the same width.". Then "For example, int32_t has the same rank as int on a 32-bit system.". 2) "Example 1 could be fixed by casting one or both operands to type unsigned int." this won't work.Mckinzie
@Mckinzie 1) The stdint.h aren't the "extended integer types", but rather typedefs corresponding to the standard integer types. "Extended integer types" refers to the case where an implementation (compiler) decides to invent its own integer types - which is allowed. 2) Sure it will, what makes you think otherwise?Flagg
1) While reading the Integer types and conversion rank section, after some search I stumbled upon this post [stackoverflow.com/questions/48011642/] where the OP mentioned "[...]So the rank of int32_t is lower than that of int.". That made me puzzle. 2) I was testing the examples here: rextester.com/BUNN55540Mckinzie
The "whenever" below the heading "usual arithmetic conversions" does not apply to the binary shift operators.Supersensitive
@RolandIllig The detailed story is that each and every operator chapter in the standard tells explicitly which promotions it uses. The shift ones are indeed among the special cases, that only integer promote the left operand and use the promoted type as the result of the expression.Flagg
@Flagg The types defined by stdint.h may be extended integer types. See C17/C18 footnote 265, C11 footnote 261, or C99 footnote 218: "Some of these types may denote implementation-defined extended integer types." Any such types would have lesser rank than a standard integer type of the same width. (An example that springs to mind - if the standard signed types are ones-complement, but there is a special twos-complement keyword to define int32_t, etc.)Kuebbing
(cont.) Although it would seem more sensible for int32_t to have higher rank than a standard 32-bit, signed, 1's complement integer (since the int32_t can represent one more value), the standard ranks them the other way round. It's a good job that WG14 are finally getting rid of non-2's complement signed integers in the next version of the standard!Kuebbing
I did my best to summarize your answer in a few simple rules and a promotion flow diagram in my answer I just added here.Aarau
6.3.1.1 N1256: "The following may be used in an expression wherever an int or unsigned int may be used: ... 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" -- where does this say the compiler is required to promote? If I use a-b and the types of a,b are unsigned short, why are a and b considered expressions where an int "may be used"?Novelist
If that is meant to be the interpretation (all integer types of lower rank than int and unsigned int are promoted to these, when used in integer arithmetic expressions) it is a tremendously lousy way of documenting it in the standard.Novelist
@JasonS It's said in normative text for each operator where it applies, for example 6.5.7 "The integer promotions are performed on each of the operands." or 6.5.5 "The usual arithmetic conversions are performed on the operands."Flagg
C
10

According to the previous post, I want to give more information about each example.

Example 1)

int main(){
    unsigned char x = 0;
    unsigned char y = 1;
    printf("%u\n", x - y); 
    printf("%d\n", x - y);
}

Since unsigned char is smaller than int, we apply the integer promotion on them, then we have (int)x-(int)y = (int)(-1) and unsigned int (-1) = 4294967295.

The output from the above code:(same as what we expected)

4294967295
-1

How to fix it?

I tried what the previous post recommended, but it doesn't really work. Here is the code based on the previous post:

change one of them to unsigned int

int main(){
    unsigned int x = 0;
    unsigned char y = 1;
    printf("%u\n", x - y); 
    printf("%d\n", x - y);
}

Since x is already an unsigned integer, we only apply the integer promotion to y. Then we get (unsigned int)x-(int)y. Since they still don't have the same type, we apply the usual arithmetic converions, we get (unsigned int)x-(unsigned int)y = 4294967295.

The output from the above code:(same as what we expected):

4294967295
-1

Similarly, the following code gets the same result:

int main(){
    unsigned char x = 0;
    unsigned int y = 1;
    printf("%u\n", x - y); 
    printf("%d\n", x - y);
}

change both of them to unsigned int

int main(){
    unsigned int x = 0;
    unsigned int y = 1;
    printf("%u\n", x - y); 
    printf("%d\n", x - y);
}

Since both of them are unsigned int, no integer promotion is needed. By the usual arithmetic converison(have the same type), (unsigned int)x-(unsigned int)y = 4294967295.

The output from the above code:(same as what we expected):

4294967295
-1

One of possible ways to fix the code:(add a type cast in the end)

int main(){
    unsigned char x = 0;
    unsigned char y = 1;
    printf("%u\n", x - y); 
    printf("%d\n", x - y);
    unsigned char z = x-y;
    printf("%u\n", z);
}

The output from the above code:

4294967295
-1
255

Example 2)

int main(){
    unsigned int a = 1;
    signed int b = -2;
    if(a + b > 0)
        puts("-1 is larger than 0");
        printf("%u\n", a+b);
}

Since both of them are integers, no integer promotion is needed. By the usual arithmetic conversion, we get (unsigned int)a+(unsigned int)b = 1+4294967294 = 4294967295.

The output from the above code:(same as what we expected)

-1 is larger than 0
4294967295

How to fix it?

int main(){
    unsigned int a = 1;
    signed int b = -2;
    signed int c = a+b;
    if(c < 0)
        puts("-1 is smaller than 0");
        printf("%d\n", c);
}

The output from the above code:

-1 is smaller than 0
-1

Example 3)

int main(){
    unsigned short a = 1;
    signed short b = -2;
    if(a + b < 0)
        puts("-1 is smaller than 0");
        printf("%d\n", a+b);
}

The last example fixed the problem since a and b both converted to int due to the integer promotion.

The output from the above code:

-1 is smaller than 0
-1

If I got some concepts mixed up, please let me know. Thanks~

Corolla answered 28/6, 2018 at 15:46 Comment(6)
Your fix to Example 2 signed int c = a+b; above invokes UB. The resultant type of a+b is unsigned, and the computed value is out of range of a signed integer.Legal
@Legal out-of-range assignment is not UBVeery
many of the examples in this answer cause UB by using the wrong format specifier, and also it makes an unwarranted assumption about the size of an intVeery
@Veery My bad! Agree, it should have been "implementation-defined or an implementation-defined signal raised". Signed overflow is UB though. It's easier to lose track of UB/IB.Legal
@Cheshar: Contrary to myth spread by some compiler maintainers, the Standard's term for actions which should be processed identically by 99.9% of implementations, but which need not be processed meaningfully by implementations where that would be impractical, is "Undefined Behavior". The term IDB is only used for actions which all implementations are supposed to process meaningfully.Bolster
I tried what the previous post recommended, but it doesn't really work. Here is the code based on the previous post: change one of them to unsigned int. No, the main answer says: Example 1 could be fixed by casting the result of the operation back to type unsigned char. So, they mean to do this, which does work to provide the expected result of 255!: printf("%u\n", (unsigned char)(x - y)); // Output: 255Aarau
A
4

Integer and floating point rank and promotion rules in C and C++

I'd like to take a stab at this to summarize the rules so I can quickly reference them. I've fully studied the question and both of the other two answers here, including the main one by @Lundin. If you want more examples beyond the ones below, go study that answer in detail as well, while referencing my "rules" and "promotion flow" summaries below.

I've also written my own example and demo code here: integer_promotion_overflow_underflow_undefined_behavior.c.

Despite normally being incredibly verbose myself, I'm going to try to keep this a short summary, since the other two answers plus my test code already have sufficient detail via their necessary verbosity.

Integer and variable promotion quick reference guide and summary

3 simple rules

  1. For any operation where multiple operands (input variables) are involved (ex: mathematical operations, comparisons, or ternary), the variables are automatically implicitly promoted as required to the required variable type before the operation is performed.
    1. Therefore, you must manually, explicitly cast the output to any desired type you desire if you do not want it to be implicitly chosen for you. See the example below.
  2. All types smaller than int (int32_t on my 64-bit Linux system) are "small types". They cannot be used in ANY operation. So, if all input variables are "small types", they are ALL first automatically implicitly promoted to int (int32_t on my 64-bit Linux system) before performing the operation.
  3. Otherwise, if at least one of the input types is int or larger, the other, smaller input type or types are automatically implicitly promoted to this largest-input-type's type.

Example

Example: with this code:

uint8_t x = 0;
uint8_t y = 1;

...if you do x - y, they first get implicitly promoted to int (which is int32_t on my 64-bit system), and you end up with this: (int)x - (int)y, which results in an int type with value -1, rather than a uint8_t type of value 255. To get the desired 255 result, manually cast the result back to uint8_t, by doing this: (uint8_t)(x - y).

Promotion flow

The promotion rules are as follows. Promotion from smallest to largest types is as follows.
Read "-->" as "gets promoted to".

The types in square brackets (ex: [int8_t]) are the typical "fixed-width integer types" for the given standard type on a typical 64-bit Unix (Linux or Mac) architecture. See, for example:

  1. https://www.cs.yale.edu/homes/aspnes/pinewiki/C(2f)IntegerTypes.html
  2. https://www.ibm.com/docs/en/ibm-mq/7.5?topic=platforms-standard-data-types
  3. And even better, test it for yourself on your machine by running my code here!: stdint_sizes.c from my eRCaGuy_hello_world repo.

1. For integer types in 64-bit x86-64 architecture CPUs

Note: "small types" = bool (_Bool), char [int8_t], unsigned char [uint8_t], short [int16_t], unsigned short [uint16_t].

SMALL TYPES: bool (_Bool), char [int8_t], unsigned char [uint8_t], short [int16_t], unsigned short [uint16_t]
--> int [int32_t]
--> unsigned int [uint32_t]
--> long int [int64_t]
--> unsigned long int [uint64_t]
--> long long int [int64_t]
--> unsigned long long int [uint64_t]

Pointers (ex: void*) and size_t are both 64-bits, so I imagine they fit into the uint64_t category above.

2. For floating point types

float [32-bits] --> double [64-bits] --> long double [128-bits]

See also

  1. https://cppinsights.io/ - a very useful tool which expands your C++ code into exactly what the compiler sees, including after applying all automatic implicit type promotion rules in the compiler.
    1. Ex: see my code from my answer here in CPPInsights.io here: https://cppinsights.io/s/bfc425f6 --> then click the play button to convert and expand it into what the compiler sees, including after applying all automatic implicit type promotion rules.

Places where I use these rules

  1. How to safely and efficiently find abs((int)num1 - (int)num2)
Aarau answered 17/6, 2022 at 5:25 Comment(0)
N
1

I would like to add two clarifications to @Lundin's otherwise excellent answer, regarding example 1, where there are two operands of identical integer type, but are "small types" that require integer promotion.

I'm using the N1256 draft since I don't have access to a paid copy of the C standard.

First: (normative)

6.3.1.1's definition of integer promotion isn't the triggering clause of actually doing integer promotion. In reality it is 6.3.1.8 Usual arithmetic conversions.

Most of the time, the "usual arithmetic conversions" apply when the operands are of different types, in which case at least one operand must be promoted. But the catch is that for integer types, integer promotion is required in all cases.

[clauses of floating-point types come first]

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.

Second: (non-normative)

There is an explicit example cited by the standard to demonstrate this:

EXAMPLE 2 In executing the fragment

char c1, c2;
/* ... */
c1 = c1 + c2;

the "integer promotions" require that the abstract machine promote the value of each variable to int size and then add the two ints and truncate the sum. Provided the addition of two chars can be done without overflow, or with overflow wrapping silently to produce the correct result, the actual execution need only produce the same result, possibly omitting the promotions.

enter image description here

Novelist answered 30/10, 2022 at 4:48 Comment(0)
T
1

In this answer I'll address the compiler flags you can use to track down bugs related to implicit type promotion since I just ran into this "feature". In the following buggy code fragment exp is of type uint32_t:

for (int32_t i = 22; i >= MAX(22 - exp + 1, 0); i--) {
    ...
}

If exp < 23 code works fine, if exp = 23 loop runs forever, and if exp > 23 loop never runs. The fix is to change the first argument to MAXto 22 - (int32_t)exp + 1. To make it easier to spot such bugs I recommend turning on the warning -Wsign-compare. It is included in -Wextra, which may be a little heavy for everyday use.

The bug in the other example;

unsigned short a = 1;
signed short b = -2;
if(a + b > 0)
    puts("-1 is larger than 0"); // will not print

is caught by -Wsign-conversion, also included in -Wextra. In my own codebase this flag produces about 40 warnings all of which are completely benign and not worth the bother to fix.

Unfortunately, neither gcc nor clang has warnings for flagging "suspicious" type promotions, but leaving safe ones be (e.g for (int i = 0; i < strlen(s); i++)).

You may want to read Friends don't let friends use "-W" for (informed) opinion on when and when not to use the compiler's warning flags.

Tachyphylaxis answered 19/7, 2023 at 14:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.