Size of Data Types

Learn about the size of various data types.

Before we start

Our aim is to refine the intuition from the “Memory” lesson in the first chapter. It would be best to reread it before continuing.

Introduction

The C language offers us a variety of data types, such as:

  • int for integers
  • float for real numbers
  • char for characters

However, there are more data types, like double and long. They don’t store a new type of data. The difference is that they allow us to store bigger numbers or have better precision.

For example, long can store a much bigger number than int can. Each data type occupies a different amount of space in memory. In other words, each data type has a different size.

To find out the size of a data type, we can use the sizeof operator. Follow the next example.

Press + to interact
#include <stdio.h>
int main()
{
printf("sizeof(char) = %d\n", sizeof(char));
printf("sizeof(short) = %d\n", sizeof(short));
printf("sizeof(int) = %d\n", sizeof(int));
printf("sizeof(float) = %d\n", sizeof(float));
printf("sizeof(double) = %d\n", sizeof(double));
return 0;
}

We see that sizeof(char) is 1, but what is the unit of measurement? Well, the output is in bytes (1 byte = 8 bits):

  • char occupies 1 byte of memory or 8 bits.
  • short occupies 2 bytes of memory or 16 bits.
  • int occupies 4 bytes of memory or 32 bits.
  • float occupies 4 bytes of memory or 32 bits.
  • double occupies 8 bytes of memory or 64 bits.

Note: It is crucial to understand that we should never assume the size of data types. It depends on the platform, compiler, and potentially other factors.

For example, in code, don’t hardcode the size of int. Instead, use sizeof(int) to retrieve it.

However, to understand the memory layout of variables, and later arrays, we need to keep the size information in mind.

For this course, we’ll assume the sizes from the above output when making memory drawings and analyzing code.

More on bits and bytes

We can see that the smallest size is 1 byte for char. Therefore, we say that the smallest addressable memory unit is the byte. It’s a fancy way of saying that we don’t have data types with a size smaller than 1 byte (8 bits).

Let’s recall an example discussed previously:

int i = 13;

For this variable, we created the following memory drawing:

We said that the operating system picks a memory location (starting point) and then fills each bit with either 1 or 0, depending on the number. There’s one issue that we didn’t solve. Smaller numbers need fewer bits, and bigger numbers need more bits. How does the computer know where to stop reading the bits for a number?

We have the starting point provided by the address of the variable. We know where to start reading. However, we don’t have the endpoint. If we just read 4 bits like the example, what if a number needs 32 bits? We’ll stop reading way too soon.

The endpoint is determined using the starting point (address) and the size of the data type. If we have an int, we know that it occupies 4 bytes = 32 bits of memory. If the starting point is at address x, the endpoint is before the address x + 32. Or, we know that the number occupies the next 32 bits from the starting point (inclusive).

So, in our example, we would need to fill all 32 bits, but our number only has 4. To solve the problem, the other bits (32-4) get filled with 0.

This process sounds harder than it is! Let’s update the drawing to clarify.

As we can see, we used 4 bytes or 32 bits. We placed 1101 and filled the other bits with 0.

We won’t create such drawings because it’s hard to draw 32 or 64 bits and then keep track of them. We only did it once to understand what happens.

Another example

Assume the following variable:

char x = 'c';

If the operating system decides to place the variable x at address 0x7ffdee2428ec, then the next 8 bits (1 byte) will be set to the binary representation of c. In binary, c is 1100011. It only needs 7 bits, but we need to fill 8. The solution is to add a 0 as padding: 01100011.

In the future, if needed, we’ll draw the bytes only, not the bits (to avoid making huge drawings). Compare the drawing below with the one for an integer with 32 bits. It’s much shorter and easier to understand.

The size of pointers

Let’s try to find the size of various pointers.

Press + to interact
#include <stdio.h>
int main()
{
printf("sizeof(char*) = %d\n", sizeof(char *));
printf("sizeof(short*) = %d\n", sizeof(short *));
printf("sizeof(int*) = %d\n", sizeof(int *));
printf("sizeof(float*) = %d\n", sizeof(float *));
printf("sizeof(double*) = %d\n", sizeof(double *));
return 0;
}

We can see that the size of a pointer, no matter its type, is 8 bytes. It’is the size needed to store all possible addresses.

When we think about it, it makes sense. The pointer is just an address to some data, so its size shouldn’t change depending on what data we store. The address ranges from 0 to the amount of memory installed in the computer, and we need enough bytes to store it.

With 8 bytes (64 bits), we can store an address ranging from 0 to 2642^{64} - 1.

Proof (optional)

We can prove that with 64 bits, we can store addresses up to 2642^{64} - 1.

To do this, we need to know the conversion process from binary to decimal. Assume the following binary number

1101

We start from the right, and for each bit, we add to a sum: bit * 2bitposition2^{bit position}.

 1    1    0    1
pos3 pos2 pos1 pos0

We end up with: 1 * 202^{0} ...