-3

I was trying to do a factorial of a 100 on C and integer overflow happened, but what I didn't understand was why my unsigned variable became negative.

int main(void)
{
    unsigned long value =1;
    for(unsigned long n = 0;n<100;n++){
        printf("This n %ld\n",value);
        value *= (n+1);
    }
    printf("%ld\n",value);
    return 0;
}

With unsigned variables, after the variable capacity is overflowed, shouldn't it start from 0 again?

The first values of value:

This current value 2  
This current value 6  
This current value 24  
This current value 120  
This current value 720  
This current value 5040  
This current value 40320  
This current value 362880  
This current value 3628800  
This current value 39916800  
This current value 479001600  
This current value 6227020800  
This current value 87178291200  
This current value 1307674368000  
This current value 20922789888000  
This current value 355687428096000  
This current value 6402373705728000  
This current value 121645100408832000  
This current value 2432902008176640000  
This current value -4249290049419214848

How can this be possible?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Ragoniis
  • 29
  • 3
  • 4
    You lied to `printf()` — you said you were passing a (signed) `long` so it took you at your word and printed the bit pattern as if it represented a signed `long`, and that bit pattern is interpreted as a negative number. Change to `%lu` and you ask it to print an `unsigned long` in decimal. (Yes: unsigned variables wraparound and restart at zero.) – Jonathan Leffler Aug 18 '19 at 03:03

1 Answers1

0

%ld in printf will interpret any number as a signed one. Every number can be read as both signed or unsigned. To print your unsigned long as unsigned you should pass %lu to printf.

The difference between an unsigned or a signed number is on which assembly instructions the compiler will choose to use when handling this variable. At assembly level there are sets of instructions designed to handle what should be a signed number and others sets of instructions to handle unsigned numbers (e.g. when you check the value of the variable through an if statement if it was declared as signed the compiler will generate some instructions and if not the compiler will generate others). char c = -1 and unsigned char c = 255 are stored as exact same values in memory.

andresantacruz
  • 1,676
  • 10
  • 17
  • The 2nd paragraph has more errors than there are holes in Swiss cheese. Most importantly the value sof `char c = -1` and `unsigned char c = 255` are **different** on x86, where char is signed - one is `-1` and one is `255`. And on ARM they are the same because both are 255. And if you mean bits in the representation, then that is implementation-defined and depends on the size of char and the signed representation if any. The CPU flags have nothing to do with undefined behaviour of signed integer overflow. – Antti Haapala -- Слава Україні Aug 18 '19 at 05:45
  • @AnttiHaapala Can you elaborate a bit more about -1 being different than 255 on x86 (considering I was talking about the value that is actually in the memory when you set -1 to a `signed char` and 255 to an `unsigned char`)? About your second argument I did not understand it at all. About the CPU flags, I was talking about how the program handles the signal. It is all handled by the compiler as it is the compiler that will create the proper instruction sets if a variable is signed or unsigned - there is no signed variable at assembly level, it is all about which instructions you will use. – andresantacruz Aug 18 '19 at 05:53
  • Forget about the processor. `-1` is `-1` and `255` is `255`, those are the **values** of the variables. – Antti Haapala -- Слава Україні Aug 18 '19 at 06:24
  • @AnttiHaapala Why should I forget about the processor if my argument revolves around how these values are stored in memory and handled by the processor? – andresantacruz Aug 18 '19 at 06:27
  • Well I updated the text trying to be clearer about what I was saying, please check if you are okay with it now @AnttiHaapala – andresantacruz Aug 18 '19 at 06:33
  • The behavior of passing an unsigned type to be formatted by `printf` with `%ld` is not defined by the C standard. It may often result in a program that prints the value as if the bits had been reinterpreted as `signed long`, but this cannot be relied on based on the C standard. – Eric Postpischil Aug 18 '19 at 11:24
  • @EricPostpischil I did not said to pass unsigned type to be formatted by printf with %ld. I said the exact opposite. – andresantacruz Aug 18 '19 at 11:28
  • @dedecos: The first sentence in this answer is “%ld in printf will interpret any number as a signed one.” That is incorrect. The behavior of passing any object not compatible with “signed long” as an argument corresponding to `%ld` is not defined by the C standard. – Eric Postpischil Aug 18 '19 at 11:32
  • @EricPostpischil I don't get the point. Doesn't `printf` will receive as argument for a `%c` specifier the exact same value if I pass `(char)-1` or if I pass `(unsigned char)255`? May you elaborate more what you are saying or throw some text that explains what you are saying? – andresantacruz Aug 18 '19 at 11:43
  • @dedecos: `%c` is different, because it involves integer promotion and an explicit conversion to `unsigned char` stated in the C standard. For `%ld, `printf` will not necessarily behave the same way whether you pass it a `signed long` or an `unsigned long`. You may have often observed it performing the same way, but that is happenstance or an artifact of how the C implementations you are using were constructed. It is not behavior that is guaranteed by the C standard. – Eric Postpischil Aug 18 '19 at 11:46
  • @dedecos: When you pass either `(char) -1` or `(unsigned char) 255`, it is promoted to an `int` when passed to `printf`. So `printf` will receive −1 or 255. Then, for `%c`, the standard says (7.21.6.1 8) that the `int` argument is converted to an `unsigned char`. So then, in an implementation with eight-bit `char`, the result will be 255 if either −1 or 255 was passed. Then `printf` prints that character. (In an implementation with wider `char`, the values will differ.) – Eric Postpischil Aug 18 '19 at 11:49
  • @dedecos: For `%ld`, the argument should be `long int`. There is no automatic promotion to another type, and there is no built-in conversion to `long int`. So, if you pass an `unsigned long`, 7.21.6.1 9 applies. Its second sentence says “If any argument is not the correct type for the corresponding conversion specification, the behavior is undefined.” – Eric Postpischil Aug 18 '19 at 11:52
  • @EricPostpischil I was reading the standard, thanks for pinpointing the sections. However 7.21.6.1 9 seems to verse about different types (e.g. %f receiving an integer). Passing an **unsigned long int** where it expects a `%ld` does not concerns `printf` as the value that will be passed to the function is the same. If I pass `(long int) -1` or `(long int)0xFFFFFFFF` (considering long int a dword) the compiler will just generate a `PUSH 0xFFFFFFFF`. The only way for `printf` to check if the argument is signed or not is by checking if its bit 32 is set or not (again considering `%ld` a dword). – andresantacruz Aug 18 '19 at 12:15
  • @EricPostpischil Please forgive if I misunderstood something very basic here and please try to point it out! Thanks in advance for the lesson! – andresantacruz Aug 18 '19 at 12:16
  • @dedecos: In both `(long int) -1` and `(long int) 0xFFFFFFFF`, there is a cast to `long int`, so the resulting value is of type `long int`, and a `long int` is passed to `printf`. This results in behavior defined by the C standard; you pass a `long int` and `printf` with `%ld` expects a `long int`. In `printf("This n %ld\n",value);` in the question, `value` is an `unsigned long` object, and there is no cast, so this passing an `unsigned long` argument where `printf` expects `long int`. The behavior of that is not defined by the C standard. – Eric Postpischil Aug 18 '19 at 12:19
  • @EricPostpischil Oh sorry about that. I meant to say `(unsigned long int)0xFFFFFFFF`- to correct: **from `printf` perspective what is the difference between passing `(long int)-1` and `(unsigned long int)0xFFFFFFFF` as argument to a `%ld` specifier as they both will be compiled to `PUSH 0xFFFFFFFF`? Doesn't `printf` receive the exact same value?** – andresantacruz Aug 18 '19 at 12:24
  • @dedecos: **Your** C implementation might have done that. And it did it for one specific version of the compiler with one specific selection of compiler switches with one specific program. Do you have any documentation that tells you the compiler will **always** do that? Even if you change the program? Even if you change the switches? Even if you upgrade the compiler to a new version? What if somebody comes along, reads this question, and relies on it in a different C implementation that does not behave that way? – Eric Postpischil Aug 18 '19 at 13:09
  • @dedecos: The C standard tells us how C implementations should behave. That gives us something to rely on: If we are using a C implementation that conforms to the C standard (at least largely conforms), we can rely on what the standard says. But here you are presuming some behavior based on observations. Nobody has promised you they will work. So you cannot rely on them. If the compiler documentation specified the behavior, you could rely on that. But only for that compiler. You should not, in an answer about C generally, assert to be true something that is not guaranteed by the C standard. – Eric Postpischil Aug 18 '19 at 13:11