Is “char foo = 255” undefined behavior if char is signed?

Summary: The result is implementation-defined and very likely to be -1, but it’s complicated, at least in principle.

The rules regarding overflow are different for operators vs. conversions, and for signed types vs. unsigned types — and the conversion rules changed between C90 and C99.

As of C90, overflow of an operator with signed integer operands (“overflow” meaning that the mathematical result cannot be represented in the expression’s type) has undefined behavior. For unsigned integer operands, the behavior is well defined as the usual wraparound (strictly speaking the standard doesn’t call this an “overflow”). But your declaration:

char foo = 255;

doesn’t use any operators (the = is an initializer, not an assignment), so none of that applies in this case.

If type char can represent the value 255 (which is true either of plain char is unsigned or if CHAR_BIT >= 9), then of course the behavior is well defined. The int expression 255 is implicitly converted to char. (Since CHAR_BIT >= 8, it’s not possible for this particular case to invoke unsigned wraparound.)

Otherwise, the conversion yields a result that can’t be stored in a char.

As of C90, the result of the conversion is implementation-defined — which means that it’s guaranteed to set foo to some value within the range of type char, and you can determine what that value is by reading the implementation’s documentation, which is required to tell you how the conversion works. (I’ve never seen an implementation where the stored value is anything other than -1, but any result is possible in principle.)

C99 changed the definition, so that an overflowing conversion to a signed type either yields an implementation-defined result or raises an implementation-defined signal.

If a compiler chooses to do the latter, then it must document which signal is raised.

So what happens if an implementation-defined signal is raised? Section 7.14 of the standard says:

The complete set of signals, their semantics, and their default
handling is implementation-defined

It’s not entirely clear (to me) what the range of possible behaviors for the “default handling” of signals is. In the worst case, I suppose such a signal could terminate the program. You might or might not be able to define a signal handler that catches the signal.

7.14 also says:

If and when the function returns, if the value of sig is SIGFPE,
SIGILL, SIGSEGV, or any other implementation-defined value
corresponding to a computational exception, the behavior is undefined;
otherwise the program will resume execution at the point it was
interrupted.

but I don’t think that applies, since an overflowing conversion is not a “computational exception” as the term is used here. (Unless the implementation-defined signal happens to be SIGFPE, SIGILL, or SIGSEGV — but that would be silly).

So ultimately, if an implementation chooses to raise a signal in response to an overflowing conversion, the behavior (not just the result) is at least implementation-defined, and there might be circumstances in which it could be undefined. In any case, there doesn’t seem to be any portable way to deal with such a signal.

In practice, I’ve never heard of an implementation that takes advantage of the new wording in C99. For all compilers I’ve heard of, the result of the conversion is implementation-defined — and very probably yields what you’d expect from a 2’s-complement truncation. (And I’m not at all convinced that this change in C99 was a good idea. If nothing else, it made this answer about 3 times as long as it would otherwise have needed to be.)

Leave a Comment

tech