Integer Overflow in C

We have seen in previous lessons that integer types in C language have a minimum value and a maximum representable value.

But what happens if we try to exceed these limits? This condition is called Overflow and in this article we will see how the C language handles such condition.

The concept of Overflow

The processors of any system, whether it be a PC, a server, a tablet or a smartphone, are capable of performing mathematical operations on integer numbers.

In particular, modern processors are capable of performing operations on integer numbers of different sizes, such as 8, 16, 32 or 64 bit integer numbers.

There is a limit to such operations, which is given by the maximum size of the integer number that the processor is capable of representing.

For example, if the processor is 16 bit, the maximum representable integer number is 2^{16} - 1 = 65535.

What happens when we try to add, using a 16 bit processor, the number 65535 with 1?

This condition is called Overflow. In Italian we could translate this term with straripamento or trabocco, precisely because the numerical value overflows from the maximum representable limit.

What happens when an Overflow occurs depends on the processor but in general the result will be an incorrect value, which does not correspond to the correct result of the operation.

Often, what happens is that the processor starts over counting from the minimum representable value. This value could be 0 or -2^{16}, depending on whether we were working with signed or unsigned integer numbers. This behavior is called Wraparound.

Returning to the example above, if we try to add 65535 with 1 we will get 0.

The reason is that if we take the binary value of 65535, that is 16 times 1:

1111111111111111_b

The correct result should be:

\begin{array}{rc} 1111111111111111_b & + \\ 0000000000000001_b & = \\ \hline {\color{red}{1}}0000000000000000_b \end{array}

that is a 1 followed by 16 zeros. But the processor is not capable of representing such value and therefore keeps exclusively the last 16 bits, which are all zeros:

\cancel{\color{red}{1}}0000000000000000_b

The final effect results as if the processor had started counting from zero again: wrap around.

Recapping:

Definition

Overflow and Wraparound

An Overflow condition occurs when the result of a mathematical operation exceeds the maximum value representable by a processor.

In this case the result of the operation will be incorrect and, in most cases, the processor will start counting again from the minimum representable value. This behavior is called Wraparound.

In general, in case of overflow most processors signal, through appropriate mechanisms towards the operating system and the program itself, that this condition has occurred. However, not all programming languages handle this signaling in the same way.

For this reason, it is necessary to understand how the C language behaves in these situations.

Integer Overflow in C Language

In case of integer overflow, the C language assumes behaviors that differ depending on whether we are working with signed or unsigned integer numbers.

Let's start from the case of unsigned integers. Let's analyze the following program:

#include <stdio.h>
#include <stdint.h>

int main() {
    uint32_t a = 4294967295;
    a = a + 1;

    printf("a = %u\n", a);

    return 0;
}

The program declares a variable a of type uint32_t, that is an unsigned 32 bit integer. We initialize a to the maximum value representable by a 32 bit integer, that is 2^{32} - 1 = 4294967295.

Subsequently, we add a with 1 and print the result.

If we execute the program, we will get:

a = 0

What happened is that the processor in overflow performed a wraparound simply returning 0. The program does not signal any error.

Now, let's try to slightly modify the program by adding to the variable, instead of 1, the value 10:

#include <stdio.h>
#include <stdint.h>

int main() {
    uint32_t a = 4294967295;
    a = a + 10;

    printf("a = %u\n", a);

    return 0;
}

If we execute the program, we will get:

a = 9

In this case too, the processor performed a wraparound, starting to count from 0 again. To be formally correct, what happened is that, if n is the number of bits, the processor returned the operation modulo 2^n.

This is the behavior dictated by the C standard in case of unsigned integer overflow.

Definition

Unsigned integer overflow in C

In C language, in case of unsigned integer overflow, the result of the operation will be the value obtained by performing a wraparound, that is starting to count again from the minimum representable value.

The result will be equal to the result of the operation modulo 2^n, where n is the number of bits of the integer.

This behavior is guaranteed by the C standard regardless of the processor or compiler.

Let's now move to the case of signed integers.

In this case, the C standard does not define a default behavior in case of overflow. This means that the behavior could vary from compiler to compiler and from processor to processor.

In general, most compilers and processors adopt the behavior of performing a wraparound, as in the case of unsigned integers.

As an example, let's try to execute the following program on a 64 bit x86 processor under the Linux operating system:

#include <stdio.h>
#include <stdint.h>

int main() {
    int32_t a = 2147483647;
    a = a + 1;

    printf("a = %d\n", a);

    return 0;
}

Trying to execute the program, we will get:

a = -2147483648

The obtained result is -2^{31}, that is the minimum value representable by a 32 bit signed integer.

Definition

Signed integer overflow in C

In C language, in case of signed integer overflow, a standard behavior is not defined. The behavior could vary from compiler to compiler and from processor to processor.

Most compilers, however, adopt the behavior of performing a wraparound, as in the case of unsigned integers.

In Summary

In this article we have seen what is meant by Overflow and how the C language handles this condition.

In particular, we have seen that:

  • In case of unsigned integer overflow, the result of the operation will be the value obtained by performing a wraparound, that is starting to count again from the minimum representable value. The result will be equal to the result of the operation modulo 2^n, where n is the number of bits of the integer.
  • In case of signed integer overflow, a standard behavior is not defined. The behavior could vary from compiler to compiler and from processor to processor.