What were the reasons for most computers which we work upon today to have an int of size 4 bytes? Does having a 4 byte int provide any efficiency for memory management, performance of a program?
This is an excellent question. Understanding this behaviour means knowing a bit about the history of the C language, and the design and development of CPUs.
The short answer is that the size difference stems from the fact that older computers were much simpler than modern computers.
In the early days of microcomputers before PCs (circa 1970), the CPUs at the time were only able to process 8 bits (1 byte) at a time, or even only 4 bits in the very early days). These were referred to as 4-bit or 8-bit CPUs because that was the size of a single unit of data which the CPU was designed to process.
This refers specifically to the size of the registers on the CPU. Registers are like variables in C, except they are built into the CPU. CPUs usually only have only a handful of registers, each with a specific name (
BX,CX`) and purpose (accessing memory, controlling ports, etc).
The number of bits that a CPU can process is proportional of the number of transistors which make up the CPU. While CPUs were still being developed (as they still are today), there was a limit to the number of transistors which could be made to fit onto a single CPU chip, and still work reliably.
As the technology improved it became possible to reliably fit more transistors onto a single chip, and thereby allow larger numbers of bits to be processed. 8-bit CPUs were eventually replaced with 16-bit CPUs, which were eventually replaced with 32-bit, and most recently 64-bit. Specialised processors, such as graphics hardware, can process numbers of 128 bits, or even larger.
What were the reasons for most computers which we work upon today to have an int of size 4 bytes?
At the time when the C language was being developed, computer processing power and memory were much more limited than they are today, and so it was especially important that code would use the least amount of resources as possible.
To run as efficiently as possible, programs and operating systems were often coded using assembly language and machine code. Two problems with this was:
- Machine code is hard to read and write, resulting in long development time and plenty of errors.
- The resulting code would only be compatible with one kind of CPU, meaning that the code would need to be re-written to run on each different type of CPU.
The creators of C wanted to be able to write code for the Unix operating system, so that the same code would compile onto many different CPUs without changes. To do this it had to be able to work regardless of the underlying bits supported by the CPU, while also working as efficiently as possible.
Around this time, 8 bit CPUs were relatively well established, 16 bit CPUs were coming into more widespread use, and 32 bit (or even 40 bit and 80 bit) CPUs were becoming available. Added to this was other complications, such as endianness, which refers to the way that the CPU interprets binary number, by which end of the number it considers to be the start of the number, either the first digit or the last.
Considering this large variety of CPUs, the decision was made to define variable based on the type of information stored in them (characters, integers, real numbers, etc), instead of relying on how the CPU would process the data. The variable type would define the minimum size that the variable could be, but the compiler for each CPU would determine the best way to encode the variable, to be in a form which would be the most efficient to process on that specific CPU.
For example, an
int is defined by the C standard to be at least 16 bits. a 32-bit CPU might process 32-bit numbers efficiently, and so an
int might be stored in 32 bits (or 4 bytes) for that CPU. A different CPU might only be able to process 16 bits at a time, and so it would store the number in that format instead.
Now you might ask why not just use 16 bits on a 32 bit CPU. The reason is that, for simplicity sake, the CPU can only process 32 bit numbers. So to process a 16 bit number, it first needs to convert the number into a 32 bits. This takes work, which takes time, resulting a perceived slower execution speed.
That said, many compilers do have options for determining how the variables should be encoded. Usually, since we care more about CPU performance than memory usage, we tell the compiler to optimize the code for whatever works best for the CPU, even if it wastes some space. So even though our code should only use an
int as if it was made up of 16 bits, on a 32-bit CPU it could be made up of 32 bits. Our code should never make use of the extra bits, and so the additional space required to store the int is effectively wasted.
We could instead tell the compiler optimise the code to use the least amount of memory, which may result in
ints being encoded as the minimum allowed size of 16 bits. This may be useful on devices where memory is extremely limited, such as embedded devices.
Another thing to consider is that modern CPUs also offer emulation modes. For compatibility purposes, modern 64-bit CPUs can run programs compiled for a 32-bit CPU. So we may compile code in 32-bit mode, even if it is intended to run on a 64-bit CPU.
So the reason why you are seeing an int as 4 bytes (32 bits), is because the code is compiled to be executed efficiently by a 32-bit CPU. If the same code were compiled for a 16-bit CPU the int may be 16 bits, and on a 64-bit CPU it may be 64 bits.
As an aside, it is for this same reason why fixed size types, such as
uint32_t are available. These are defined to be an exact number of bits regardless of the underlying CPU.
Does having a 4 byte int provide any efficiency for memory management...
The size of the int may indirectly affect the efficiency of the memory access, and this has less to do with the size of the int, and more to do with how it is laid out in memory. This is referred to as alignment.
The bit size of a CPU usually correlates with the size of memory which is most efficient for the CPU to access. For example, a 32-bit CPU might be most efficient at accessing memory in blocks of 4 bytes. As long as the ints are positioned at multiples of 4 bytes, the CPU would have an easy time reading the data, and would process it most efficiently.
Issues with alignment most often arise when storing data in structs. Variables in a struct can often be of different sizes, resulting in parts of the struct not aligning on multiples of memory which is efficient for the CPU.
To solve this, the compiler will usually insert invisible blank data between the variables of the struct, to shift them around so that they align efficiently memory. In some cases this may result in undesirable or unpredictable behaviour, and it may be necessary to manually insert placeholder variables to shift the data around yourself. In such cases it is often better to use fixed size variables, or re-design the struct so that the variables are ordered more efficiently.
..., performance of a program?
The answer depends on many things. On older CPUs, the choice of variable would make a significant difference in how efficiently the CPU would handle the variable. For example, on a 32-bit CPU, it would be significantly more efficient to process numbers of 32-bits (or 4 bytes), since a single number could fit into a single register on the CPU.
Nowadays other factors are arguably more important. Modern multi-core CPUs are often able to process calculations faster than the data can be loaded from memory, and so the priority is usually around making sure data flows as efficiently as possible.