3

I'm working on a project to update 20 year old code, and many of the issues have to do with integer overflow. I wanted to make sure I was testing for overflow correctly, so I wrote a test program. It's output supprised me. Here it is:

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

int main (void) {

   size_t largerNum,Num;

   largerNum = 12;
   Num = UINT_MAX;

   printf("largerNum = %u\nNum = %u\nNum + 1 = %u\n", largerNum    , Num, Num + 1);

   largerNum = Num + 1;

   printf("largerNum now = %u\n", largerNum);

   if(largerNum < Num ){
      printf("largerNum overflowed to %u\n", largerNum);
   }
   else {
      printf("largerNum did not overflow: %u\n", largerNum);
   }

   printf("Is (0 < UINT_MAX)?\n");

   (0 < UINT_MAX)?printf("YES\n"):printf("NO\n");

   printf("Is (largerNum < Num)?\n");

   (largerNum < Num)?printf("YES\n"):printf("NO\n");

   return 0;
}

And its output:

[afischer@susm603 /home/afischer/Fischer_Playground/overflowTest]$ main
largerNum = 12
Num = 4294967295
Num + 1 = 0
largerNum now = 0
largerNum did not overflow: 0
Is (0 < UINT_MAX)?
YES
Is (largerNum < Num)?
NO

I've looked at some other posts here and here and read this paper, but it has not made the output anymore clear. Anyone see this before?

Edit: I got it to work when changing from size_t to unsigned long, which shouldn't do anything.

  6 int main (void) {
  7 
  8    unsigned long largerNum,Num;
  9 
 10    largerNum = 12;
 11    Num = UINT_MAX;
 12 
 13    printf("largerNum = %u\nNum = %u\nNum + 1 = %u\n", largerNum    , Num, Num + 1);
 14 
 15    largerNum = Num + 2;
 16 
 17    printf("largerNum now = %u\n", largerNum);
 18 
 19    if(largerNum < Num ){
 20       printf("largerNum overflowed to %u\n", largerNum);
 21    }
 22    else {
 23       printf("largerNum did not overflow: %u\n", largerNum);
 24    }
 25 
 26    printf("Is (0 < UINT_MAX)?\n");
 27 
 28    (0 < UINT_MAX)?printf("YES\n"):printf("NO\n");
 29 
 30    printf("Is (largerNum < Num)?\n");
 31 
 32    (largerNum < Num)?printf("YES\n"):printf("NO\n");
 33 
 34 
 35    printf("largerNum = %u\n", largerNum);
 36    printf("Num = %u\n", Num);
 37 
 38    return 0;
 39 }

Output:

[afischer@susm603 /home/afischer/Fischer_Playground/overflowTest]$ main
largerNum = 12
Num = 4294967295
Num + 1 = 0
largerNum now = 1
largerNum overflowed to 1
Is (0 < UINT_MAX)?
YES
Is (largerNum < Num)?
YES
largerNum = 1
Num = 4294967295

Edit2:

Upon reading some comments, I substituted 'UINT_MAX' for 'ULONG_MAX', and the ternary operators functioned correctly. I then changed 'size_t' to 'unsigned long'. and it still works correctly. What is odd to me is that on my machine, 'size_t', 'unsigned int', and 'unsigned long' are all the same number of bytes, and 'UINT_MAX' and 'ULONG_MAX' are the same value, yet that ternary operator would still fail despite everything being the same. Maybe it's not the same? This upsets my understanding of C.

For those interested, the working code:

  6 int main (void) {
  7    /* Can be size_t or unsigned long */
  8    size_t largerNum,Num;
  9 
 10    largerNum = 12;
 11    Num = ULONG_MAX;
 12 
 13    printf("largerNum = %u\nNum = %u\nNum + 1 = %u\n", largerNum    , Num, Num + 1);
 14 
 15    largerNum = Num + 2;
 16 
 17    printf("largerNum now = %u\n", largerNum);
 18 
 19    if(largerNum < Num ){
 20       printf("largerNum overflowed to %u\n", largerNum);
 21    }
 22    else {
 23       printf("largerNum did not overflow: %u\n", largerNum);
 24    }
 25 
 26    printf("Is (0 < ULONG_MAX)?\n");
 27 
 28    (0 < ULONG_MAX)?printf("YES\n"):printf("NO\n");
 29 
 30    printf("Is (largerNum < Num)?\n");
 31 
 32    (largerNum < Num)?printf("YES\n"):printf("NO\n");
 33 
 34    
 35    printf("largerNum = %u\n", largerNum);
 36    printf("Num = %u\n", Num);
 37    
 38    return 0;
 39 }

Output:

[afischer@susm603 /home/afischer/Fischer_Playground/overflowTest]$ main
largerNum = 12
Num = 4294967295
Num + 1 = 0
largerNum now = 1
largerNum overflowed to 1
Is (0 < ULONG_MAX)?
YES
Is (largerNum < Num)?
YES
largerNum = 1
Num = 4294967295

Final Edit:

After reading more comments, I found that my printf() statements were wrong. Thank you all for your help, and everything makes way more sense now. =D

Final code:

  6 int main (void) {
  7 
  8    unsigned long largerNum,Num;
  9 
 10    largerNum = 12;
 11    Num = ULONG_MAX;
 12 
 13    printf("largerNum = %zu\nNum = %zu\nNum + 1 = %zu\n", larger    Num, Num, Num + 1);
 14 
 15    largerNum = Num + 2;
 16 
 17    printf("largerNum now = %zu\n", largerNum);
 18 
 19    if(largerNum < Num ){
 20       printf("largerNum overflowed to %zu\n", largerNum);
 21    }
 22    else {
 23       printf("largerNum did not overflow: %zu\n", largerNum);
 24    }
 25 
 26    printf("Is (0 < ULONG_MAX)?\n");
 27 
 28    (0 < ULONG_MAX)?printf("YES\n"):printf("NO\n");
 29 
 30    printf("Is (largerNum < Num)?\n");
 31 
 32    (largerNum < Num)?printf("YES\n"):printf("NO\n");
 33 
 34 
 35    printf("largerNum = %zu\n", largerNum);
 36    printf("Num = %zu\n", Num);
 37 
 38    return 0;
 39 }

Final Output:

[afischer@susm603 /home/afischer/Fischer_Playground/overflowTest]$ main
largerNum = 12
Num = 18446744073709551615
Num + 1 = 0
largerNum now = 1
largerNum overflowed to 1
Is (0 < ULONG_MAX)?
YES
Is (largerNum < Num)?
YES
largerNum = 1
Num = 18446744073709551615
Cœur
  • 37,241
  • 25
  • 195
  • 267
  • Which part surprises you exactly? – Luchian Grigore May 20 '15 at 16:13
  • related: http://stackoverflow.com/questions/199333/how-to-detect-integer-overflow-in-c-c – NathanOliver May 20 '15 at 16:14
  • isn't the type of `size_t` dependent on the machine you are running on? maybe the max of `size_t` is greater than `UINT_MAX`? – dwcanillas May 20 '15 at 16:15
  • @Luchian `largerNum now = 0` and on the very next line `0 < UINT_MAX` returns false. Anyway, I cannot reproduce the result on VS2015, using the 32-bit compiler. – Praetorian May 20 '15 at 16:16
  • @Praetorian I couldnt reproduce it either – dwcanillas May 20 '15 at 16:16
  • 1
    In `(0 < UINT_MAX)` does your compiler consider `0` to be `int`? If so, `(0 < 0xFFFFFFFF)` is false. – Weather Vane May 20 '15 at 16:17
  • 4
    I think `size_t` is 64-bit on your platform, but you're using the wrong `printf` format specifier, and thus truncating. Since this is tagged C++ as well, use `cout` for printing, and your problems will probably go away. – Praetorian May 20 '15 at 16:18
  • 1
    @Praetorian I think you should build an answer out of this, `%u` is simply not the correct way to display `size_t`. – Angew is no longer proud of SO May 20 '15 at 16:20
  • @Praetorian in MSVC `limits.h` it is `#define UINT_MAX 0xffffffff` – Weather Vane May 20 '15 at 16:22
  • @WeatherVane Never mind what I said earlier, it doesn't matter anyway. That literal is too big to fit into an `int`, so it'll be promoted to an `unsigned int`. – Praetorian May 20 '15 at 16:25
  • @Praetorian pls ignore mine too since OP does not dispute `(0 < 0xFFFFFFFF)` – Weather Vane May 20 '15 at 16:27
  • @Praetorian just got there, upvoted you: when I compiled/ran OP's code in MSVC, I got the correct result for the last test: `Is (largerNum < Num)? YES` which suggests his `size_t` is different to mine. – Weather Vane May 20 '15 at 16:30
  • Making it work with 'unsigned long' shows: it is an printf issue Please fix it, better get rid of these.. –  May 20 '15 at 16:46
  • Thank you all for your help! I've gone through some of these comments and incorporated some of the information into the code. One of the things i realized is that for sake of completeness `UINT_MAX` should have been `ULONG_MAX`, but that doesn't explain why changing the original code stipit from `size_t` to `unsigned long` would allow the code to function correctly. –  May 20 '15 at 16:49
  • @DieterLücking Is there any reason why an `unsigned long` would be treated differently from `size_t`? Not disputing that it is treated differntly, just looking for a history lesson or explaination of printf's internal workings i guess. –  May 20 '15 at 16:52
  • @Praetorian I'm here! just reading and testing! =) –  May 20 '15 at 16:54
  • @Makenbaccon 32 vs. 64 bit (size_t) is the difference –  May 20 '15 at 16:54
  • @DieterLücking I updated my code to reflect proper usage of `printf()`, and huzzah! there it is, all the numbers I wasn't seeing. =D Thank you for your help. –  May 20 '15 at 17:02

2 Answers2

5

My guess is your platform has a 64-bit size_t, and you're using the wrong format specifier to print a size_t, which is undefined behavior and is resulting in the misleading output.

To print size_ts, use %zu on gcc and clang, and %Iu on MSVC. Or forget all that and use std::cout to print the results.

Using %Iu on VS2015, the output I get on a 64-bit compiler is

largerNum = 12
Num = 4294967295
Num + 1 = 4294967296
largerNum now = 4294967296
largerNum did not overflow: 4294967296
Is (0 < UINT_MAX)?
YES
Is (largerNum < Num)?
NO
Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • I'm going to go look up `%zu` right now. I'm more confused why changing `size_t` to `unsigned long` would make my code work. –  May 20 '15 at 16:47
  • Is there any reason why an `unsigned long` would be treated differently from `size_t`? Not disputing that it is treated differntly, just looking for a history lesson or explaination of printf's internal workings i guess. –  May 20 '15 at 16:53
  • @Makenbaccon `unsigned long` may differ in range from `size_t`. Same size does not mean same range - although _that_ is rare. – chux - Reinstate Monica May 20 '15 at 17:01
  • Disregard all of my ignorance. I've updated the final code with those pesky `%zu` entries and huzzah! everything makes sense. Thank you for your time. =) –  May 20 '15 at 17:03
  • @Makenbaccon Try printing the sizes of `unsigned int`, `unsigned long` and `size_t` (remember to use `%zu` to print the result of `sizeof` :)). The first two are probably 4 bytes each and the last 8. – Praetorian May 20 '15 at 17:15
0

Just adding to @Praetorian's answer and showing a type safe implementation:

#include <iostream>
#include <limits>

int main (void) {
   using std::size_t;
   using std::cout;

   size_t largerNum = 12;
   size_t Num = std::numeric_limits<size_t>::max();

   cout << "largerNum = " << largerNum << "\nNum = " << Num << "\nNum + 1 = " << Num + 1 << "\n";
   largerNum = Num + 1;
   cout << "largerNum now = " << largerNum << "\n";

   if(largerNum < Num ){
       cout << "largerNum overflowed to " << largerNum << "\n";
   }
   else {
       cout << "largerNum did not overflow: " << largerNum << "\n";
   }

   cout << "Is (0 < Unsigned Maximum)?\n";

   (0 < std::numeric_limits<size_t>::max())?cout << "YES\n":cout << "NO\n";

   cout << "Is (largerNum < Num)?\n";

   (largerNum < Num)?cout << "YES\n":cout << "NO\n";

   return 0;
}

Hence: 'printf' a not a good choice in C++, it is not type safe (although good compilers can recognize invalid format specifiers). On the other hand the iostream operators are cumbersome (for many) and are bad if an output has to be translated to different languages (eg.: gnu getline). You may browse the net for a type safe format string (eg.: boost::format)

  • Thank you, this will effect how I code in c++. I originally tagged this c++, but that was a mistake. I'll have to look into boost::format. Thanks again for your help! –  May 20 '15 at 17:44