Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is a char value set to CHAR_MAX guaranteed to wrap around to CHAR_MIN?

My code:

#include <stdio.h>
#include <limits.h>

int main()
{
    char c = CHAR_MAX;
    c += 1;
    printf("CHAR_MIN=%d CHAR_MAX=%d c=%d (%c)\n", CHAR_MIN, CHAR_MAX, c, c);
}

Output:

CHAR_MIN=-128 CHAR_MAX=127 c=-128 ()

We see that when we increment a char variable set to CHAR_MAX, it wraps around to CHAR_MIN. Is this behavior guaranteed? Or is it going to be undefined behavior or implementation-specified behavior? What does the C99 standard say about this?

[Note: What happen when give value bigger than CHAR_MAX (127) to char or C- why char c=129 will convert into -127? does not address this question because they talk about assigning an out-of-range value not incrementing a value to an out-of-range value.]

like image 270
Lone Learner Avatar asked Mar 08 '20 11:03

Lone Learner


1 Answers

The question is twofold: Firstly, is

char c = CHAR_MAX;
c += 1;

evaluated differently from

char c = CHAR_MAX;
c = c + 1;

and the answer is no it is not, because C11/C18 6.5.16.2p3:

  1. A compound assignment of the form E1 op = E2 is equivalent to the simple assignment expression E1 = E1 op (E2) except that the lvalue E1 is evaluated only once, and with respect to an indeterminately-sequenced function call, the operation of a compound assignment is a single evaluation. If E1 has an atomic type, compound assignment is a read-modify-write operation with memory_order_seq_cst memory order semantics. 113)

Then, the question is what happens in c = c + 1. Here the operands to + undergo usual arithmetic conversions, and c and 1 are therefore promoted to int, unless a really wacky architecture requires that char is promoted to unsigned int. The calculation of + is then evaluated, and the result, of type int/unsigned int is converted back to char and stored in c.

There are 3 implementation-defined ways in which this can then be evaluated:

  • CHAR_MIN is 0 and therefore char is unsigned.

    Either char is then promoted to int or unsigned int and if it is promoted to an int, then CHAR_MAX + 1 will necessarily fit into an int too, and will not overflow, or if unsigned int it may fit or wrap around to zero. When the resulting value, which is numerically either CHAR_MAX + 1 or 0 after modulo reduction, back to c, after modulo reduction it will become 0, i.e. CHAR_MIN

  • Otherwise char is signed, then if CHAR_MAX is smaller than INT_MAX, the result of CHAR_MAX + 1 will fit an int, and the standard C11/C18 6.3.1.3p3 applies to the conversion that happens upon assignment:

    1. Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised.
  • Or, iff sizeof (int) == 1 and char is signed, then char is promoted to an int, and CHAR_MAX == INT_MAX => CHAR_MAX + 1 will cause an integer overflow and the behaviour will be undefined.

I.e. the possible results are:

  • If char is an unsigned integer type, the result is always 0, i.e. CHAR_MIN.

  • Otherwise char is a signed integer type, and the behaviour is implementation-defined/undefined:

    • CHAR_MIN or some other implementation-defined value,
    • an implementation-defined signal is raised, possibly terminating the program,
    • or the behaviour is undefined on some platforms where sizeof (char) == sizeof (int).

All increment operations c = c + 1, c += 1, c++ and ++c have the same side effects on the same platform. The evaluated value of the expression c++ will be the value of c before the increment; for the other three, it will be the value of c after the increment.