0

We know that signed char can have values only from -128 to 127. but when we run the below program no overflow happens even though the l output exceeds the range of signed character.

#include <stdio.h>


int main()
{
    char i = 60;
    char j = 30;
    char k = 10;
    char l = (i*j)/k;
    printf("%d ", l);

    return 0;
}

the output of l is 180 which is out of range for char l, but i am not getting any error.

in other scenario if we take the same program but instead of arithmetic function if we simply put l=180 and try to print it then we get wrong answer.

#include <stdio.h>


int main()
{
    char i = 60;
    char j = 30;
    char k = 10;
    char l = 180;
    printf("%d ", l);

    return 0;
}

the answer that i get in 2nd case is -76. can anyone explain it why? even if i am virtually executing the same thing but i am getting different result.

EDIT:

This is the classic example that the intermediate computations are made in int and not char

so in 1st when i am doing computation it is taking all values in int and in the later part i am explicitly mentioning it as char.

Vishwa Ratna
  • 5,567
  • 5
  • 33
  • 55
  • Maybe your compiler is broken ? What compiler do you use ? – marcolz Jul 04 '16 at 14:00
  • 3
    The first snippet is not reproducible: https://ideone.com/B4b4AJ – Eugene Sh. Jul 04 '16 at 14:00
  • 2
    I would say it's the optimization. The expression is just evaluated as `int` right in the print statement. Or you are not telling the truth. – Eugene Sh. Jul 04 '16 at 14:01
  • @EugeneSh. Why is the optimizer allowed to break truncation to char ? – marcolz Jul 04 '16 at 14:02
  • @marcolz , no my compiler is not broken , i have tested the above program on two ide say DEVC++ and VS2015 , even you can try the above two version of same program and can yourself see that the answers are differing in-spite they are saying the same logic. – Vishwa Ratna Jul 04 '16 at 14:02
  • @marcolz Not sure it is allowed.. it's rather the second part of the comment. – Eugene Sh. Jul 04 '16 at 14:04
  • @ Eugene Sh , sir you can yourself check them by running on any of your IDE , i am getting different results from both program. – Vishwa Ratna Jul 04 '16 at 14:04
  • reproducable on SuSE linux on IBAN zSeries gcc (SUSE Linux) 4.3.4, optimization level -O3 – Ingo Leonhardt Jul 04 '16 at 14:04
  • @IngoLeonhardt Does it exhibit the same without optimization? – Eugene Sh. Jul 04 '16 at 14:05
  • On gcc 4.8.4 I can only reproduce it when I specify unsigned char. – marcolz Jul 04 '16 at 14:06
  • It's 180 even with -O0 on IBM Z – Ingo Leonhardt Jul 04 '16 at 14:07
  • 2
    Integer overflow is UB. Your compiler's optimizer uses it to eliminate the l variable completely. Fast always trumps accurate. Doesn't work in the 2nd case since the compiler has already generated -76. – Hans Passant Jul 04 '16 at 14:07
  • 2
    @HansPassant There is no overflow in the calculation itself, only in the conversion to `char`. And conversion of a value that doesn't fit in a smaller signed integer type is implementation-defined, not UB. – interjay Jul 04 '16 at 14:10
  • 1
    @interjay But implementation-defined behavior can't explain the described behavior, as it is not about casting only here.. – Eugene Sh. Jul 04 '16 at 14:13
  • It turns to be an interesting question. – Eugene Sh. Jul 04 '16 at 14:19
  • @EugeneSh. I didn't offer an explanation (I see no other explanation than a compiler bug or a mistake by OP), I only said that it isn't UB. – interjay Jul 04 '16 at 14:19
  • http://stackoverflow.com/questions/18195715/why-is-unsigned-integer-overflow-defined-behavior-but-signed-integer-overflow-is states that it *is* UB though, as the result of the calculation is a signed integer. – marcolz Jul 04 '16 at 14:20
  • 1
    @marcolz But where is the overflow here? – Eugene Sh. Jul 04 '16 at 14:21
  • 1
    The result should be the same. This is a compiler (optimization) bug. – 2501 Jul 04 '16 at 14:27
  • @2501 Now I *think* the problem is with the fact that `printf`s `va_arg` do not actually have any type information, so the integer promotion we *would* expect doesn't actually happen, making it either UB or unspecified. Not a bug. The difference is in the memory content extending `l` – Eugene Sh. Jul 04 '16 at 14:35
  • @EugeneSh. the overflow is trying to fit 180 into a (presumed) signed char. That would make this UB and thus the results need not be the same. va_arg actually does promote integer types smaller than int to int. – marcolz Jul 04 '16 at 14:38
  • @2501 Exactly. This is the problem here. It expects `int` for `%d`, so reads the whole `int` memory in place of `char`. – Eugene Sh. Jul 04 '16 at 14:40
  • @marcolz Read the thread. It is implementation defined. – 2501 Jul 04 '16 at 14:40
  • @2501 oh ? http://stackoverflow.com/questions/23983471/char-is-promoted-to-int-when-passed-through-in-c http://stackoverflow.com/questions/22844360/what-default-promotions-of-types-are-there-in-the-variadic-arguments-list – marcolz Jul 04 '16 at 14:42
  • @EugeneSh. Actually I have misspoken. Var-args function performs default argument promotions which include integer promotions. – 2501 Jul 04 '16 at 14:42
  • @2501 Hm. In this case I can't come up with anything but a bug.. – Eugene Sh. Jul 04 '16 at 14:44
  • I just found your comment stating that converting 180, which is an int to signed char is UB. It isn't because C11 6.3.1.3.p3 states it is implementation defined. – 2501 Jul 04 '16 at 14:45
  • The owner accepted the answer that stated that is claim was false, in the mean time.. – marcolz Jul 04 '16 at 14:49
  • Another proof that something starting with "We know that ..." or similar phrase is wrong >95% of the times. whether `char` is signed is implementation dependent. And `signed char` has a **minimum range of `-127..127`. can be much larger, though. – too honest for this site Jul 04 '16 at 14:49
  • @marcolz I would rather see his explicit comment about it. Also see Ingo Leonhardts comments above. – Eugene Sh. Jul 04 '16 at 14:50
  • 3
    Please state your target platform (CPU, OS, compiler details) – too honest for this site Jul 04 '16 at 15:12

3 Answers3

3

The C language is actually unable to perform any form of calculations on char types. char is a small integer type, meaning that whenever it appears as part of an expression, it always gets implicitly promoted to int. This is called integer promotion or the integer promotion rules.

In addition, there is another implicit type conversion back to char, since you store the result, which is of type int, inside a char. This is called lvalue conversion: it converts the result of an expression to the type of the designated object.

char l = (i*j)/k;

is therefore completely equivalent to

char l = (char)( (int)i * (int)j / (int)k );

Since all arithmetic is carried out on type int, there are no overflows. The result 180 may or may not fit inside a char.

You should avoid using char for any form of arithmetic, because it has implementation-defined signedness. On one system it might be signed, -128 to 127, on another system it might be unsigned, 0 to 255. Use uint8_t instead, it is a better and safer type.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • 1
    While good as a general suggestion, it still offers no explanation to the behavior exhibited. the second snippet clearly suggests that `180` does not fit into the OPs `char`. – Eugene Sh. Jul 04 '16 at 15:01
  • An alterrnative is to use `unsigned char` wherever an arithmetic result has to stored. That guarantees the same or larger range as `uint8_t`. – too honest for this site Jul 04 '16 at 15:07
1

Are you sure you did and test what you described in the first snippet? If it is run, it overflows wraps around as expected.

It makes a big difference if you did

char l = (i*j)/k;
printf("%d ", l);

And if you did

printf("%d ", (i*j)/k);

As in the second case, the compiler would infer the usage of an int as the output, (the result could not fit inside a char without wrapping around obviously), and primarily because the intermediate computations are made in int and not char.

In addition, consider that the result actually fits inside an unsigned char, so check this case too.

In your case, I would start thinking cases of a problematic compiler too, but only after I am sure what the program executed should do.

Nick Louloudakis
  • 5,856
  • 4
  • 41
  • 54
  • @JonathanLeffler This actually can be an answer, even if not explicitly; the answer to this answer is the answer to thw question. – Paul Stelian Jul 04 '16 at 14:08
  • I really don't see how this answers the question. It might answer a different question, but not this one. – Eugene Sh. Jul 04 '16 at 14:48
  • 4
    There is no overflow in the code. Jut conversion, which **may be** implementation defined for this constellation, but is not UB. – too honest for this site Jul 04 '16 at 15:14
  • It is right that there is no overflow, I should mention wrapping around instead. – Nick Louloudakis Jul 04 '16 at 16:49
  • @NickL. I am not the downvoter, though as I said, I don't see how the answer is explaining what the OP apparently sees. But the ultimate answer should. And I am unable to suggest how to improve it as I don't know the correct answer as probably you don't. That's it. – Eugene Sh. Jul 04 '16 at 16:56
  • @EugeneSh. You are right, and I think that the problem is in the question itself. I ran the code posted, and the result was different - and already mentioned by others. I supposed there is a possibility that the OP posted some code he was remembering from a meeting and/or a conversation but not exactly as it was. This pushed me to try guessing what he was meaning, and then attempted to answer it, by attempting to cover all cases. But I agree this is not the best answer given, and I doubt there is one. – Nick Louloudakis Jul 04 '16 at 17:26
-2

This happens because intermediate calculations are with int, not with char. char may be signed or unsigned, but the specs themselves say char, signed char and unsigned char are 3 distinct types.

EDIT: Found out why this happens: Signed char overflows, which triggers undefined behavior. That's why you don't get a constant. Signed integral types (signed char, signed int, signed long) all trigger undefined behavior on overflow. Optimization is allowed to do anything, although it mostly consists on ignoring the existence of the overflow.

Paul Stelian
  • 1,381
  • 9
  • 27
  • 1
    The intermediate calculations are with `int`, but the assignment to `char l` will force a conversion to `char`. The actual value written is implementation-defined but I don't see how it could be 180 (assuming signed `char`). – interjay Jul 04 '16 at 14:08
  • int is at least 16 bits and can hold the product well enough. char may be unsigned. – Paul Stelian Jul 04 '16 at 14:09
  • 1
    Yes, `int` can hold the product, but that doesn't help if you then convert it to `char` as is done here. And the second example shows that `char` is signed on OP's machine. – interjay Jul 04 '16 at 14:11
  • The division takes two ints, 1800 and 10 and gives 180; this 180 itself is required to fit the char, not the intermediate values. – Paul Stelian Jul 04 '16 at 14:12
  • "this 180 itself is required to fit the char," - But it *doesn't* fit in signed char. – interjay Jul 04 '16 at 14:13
  • 2
    Allowing both values to exist though is a weird thing indeed (as char is only 8 bits, 180 and -76 are equivalent) – Paul Stelian Jul 04 '16 at 14:13
  • @PaulStelian: While likely true here, `char` is not required to have 8 bits. OP does not mention the target platform. – too honest for this site Jul 04 '16 at 14:59
  • char is required to be 1 byte, and 1 byte is 8 bits in most cases. @Olaf – Paul Stelian Jul 04 '16 at 14:59
  • @PaulStelian Please state a reference to where the standard defines a byte as 8 bits! (hint: you will not find any!) For the "most cases" see the first part of my comment. Still there is no UB.. – too honest for this site Jul 04 '16 at 15:02