I am delighted to serve you and honored by your trust
In today's article, we introduce you to machine language—the language of zeros and ones—which sits at the lowest level of programming languages. It is used for direct communication and interaction with computer hardware. Together, we will discover the advantages of machine language, the difference between machine language and assembly language, and review examples of machine language, its uses, and how its instructions are executed by various computers.
Before we learn what machine language is, let’s clarify: What is a programming language? And what are the levels that programming languages passed through during their development before evolving into many types and names?
A programming language is the method adopted by humans to communicate with computers and other programmable devices to request the execution of commands. It is written or formulated as a series of sequential commands, saved in the form of computer programs or applications, and then passed to a computer to execute them and obtain the required results.
During their development, programming languages have passed through several stages or levels, most notably:
Low-Level Languages
Machine Language
Assembly Language
Mid-Level Languages
High-Level Languages
Let’s learn about the features and characteristics of each of these languages.
The languages of the first generation of computers were low-level languages consisting of a set of instructions entered into the computer in a form directly executable by the computer processor. The code consisted of numbers written in the binary system (base 2) or the hexadecimal system (base 16), and these programs could be executed directly.
For example, the following sequence of numbers is just an example of a piece of code written in machine language for a computer program that adds two numbers:
11101100, 11011010, 10100001, 11011101, 01011111, 10111110,
11101110, 00101101, 11010111, 11101100, 11011011, 11101110,
11101110, 11011101, 00001010, 10101011, 11101110, 11101110,
11001110, 10111010, 10101010, 10101101, 11101010, 11101111,
00011010, 11010010, 11101110, 00011010, 10101011, 11101110,
11101110, 10101110, 11101010, 11101011, 11101010, 00001000,
00001010, 11101110, 11001010
The previous program instructions are written in the binary system. If we used the hexadecimal system to write the instructions, the program would look like this:
0xEC, 0xDA, 0xA1, 0xDD, 0x5F, 0xBE,
0xEE, 0x2D, 0xD7, 0xEC, 0xDB, 0xEE,
0xEE, 0xDD, 0x0A, 0xAB, 0xEE, 0xEE,
0xCE, 0xBA, 0xAA, 0xAD, 0xEA, 0xEF,
0x1A, 0xD2, 0xEE, 0x1A, 0xAB, 0xEE,
0xEE, 0xAE, 0xEA, 0xEB, 0xEA, 0x08,
0x0A, 0xEE, 0xCA
As you can see, writing and reading machine language was extremely difficult and complex. For this reason, second-generation languages, known as assembly languages, appeared. These are also low-level languages, but their code is somewhat easier to understand. Computer scientists created assembly language to serve as an intermediate language that is easier to read. It includes a limited set of instructions for direct interaction with hardware and programmable electronic equipment. It varies according to the architecture of each processor; every generation of processors has its own assembly language.
For example, the following code shows a program written in assembly language to add two numbers, designed to run on an Intel x86 processor, adding the numbers 5 and 7:
section .data
num1 dd 5
num2 dd 7
result dd 0
section .text
global _start
_start:
mov eax, [num1]
add eax, [num2]
mov [result], eax
; Exit the program
mov eax, 1 ; System call number for exit
xor ebx, ebx ; Exit status 0
int 0x80 ; Make system call
If you read the previous code, you will conclude that the first part, section .data, defines the numbers num1 and num2 (the inputs to be added), as well as a result variable to store the sum. The _start section points to the beginning of the program.
Then the actual instructions begin: the instruction mov eax, [num1] moves the value stored in num1 to the eax register, and the instruction add eax, [num2] adds the value stored at the address of num2 to this register. Afterward, the value of the register is moved to the result variable. The last three instructions are used to exit the program.
As you can see, this language is more understandable than machine language filled with zeros and ones, but it is still difficult and confusing. It requires the programmer to care about minute details, such as determining which registers will be used to move data during execution. It is also not suitable for developing advanced programs and algorithms and produces a large amount of code for every operation.
After assembly language, other programming languages were developed, such as BASIC, FORTRAN, Pascal, C, and C++, which were called mid-level programming languages. These languages sit in a middle ground between low-level languages and high-level languages like Python or Java. They allow programmers to control fine hardware details while providing a higher level of flexibility.
For example, to add two numbers in the C programming language, the code would look like this:
C
#include <stdio.h>
int main() {
// Define variables
int num1 = 5;
int num2 = 7;
int result;
// Add numbers
result = num1 + num2;
// Print result
printf("The result is: %d\n", result);
return 0;
}
Finally, third-generation or high-level programming languages appeared, such as Python, Ruby, and JavaScript, to simplify programming for developers. These languages are considered among the easiest and their instructions are written in vocabulary close to spoken English. They are easy for humans to read and understand and do not force us to deal with minute hardware details.
However, these languages need to be translated or interpreted for the computer processor to understand and execute them correctly. These languages are not tied to a specific type of device; their commands can be converted into different machine language instructions depending on each device.
For example, the following Python code performs the same function as the previous code for adding two numbers. You can notice the simplicity of the language and how close it is to our spoken words compared to the previous codes:
Python
# Define variables
num1 = 5
num2 = 7
# Add numbers
result = num1 + num2
# Print result
print("The result is:", result)
Machine language, or the language of zeros and ones, is a programming language consisting of commands and instructions written in a way that the computer can understand and process. Computers actually only understand zeros and ones, where the number 1 means the presence of an electric current or voltage, and the number 0 means the absence of it.
In the early days of computers, these numbers were converted into commands and then executed directly by the Central Processing Unit (CPU). Each command was a numerical code or a series of numerical codes represented in binary or hexadecimal. It should be noted that these numerical codes are specific to each device; machine language was developed for each specific processor architecture.
The only way to enter these instructions into computers in their early days was through punched cards. Early computers were not yet capable of storing data in files, so if you wanted to write a program in machine language and enter it into the computer, the only way was to use these punched cards.
Each card contained holes that expressed specific instructions for the computer. If a hole existed, it represented the value 1; if there was no hole, it represented 0. These cards had many forms, but the most famous was the 80-column card developed by IBM in 1928.
Imagine that you would need one similar card for every line of code! Writing a full program using these cards was extremely difficult and required a massive set of cards. These cards were passed sequentially to a dedicated device called a card reader, which read the instructions column by column, converted the sequence of holes into digital information, and then loaded them into the computer's memory. After reading and loading all the cards, the computer would execute the instructions and output the results—also in the form of punched cards.
You can imagine how much effort early programmers put into the process of programming and communicating with the computer. This mechanism continued until magnetic disks and peripherals for storing and entering data appeared, followed by personal computers, which abolished the use of punched cards and made them part of the history of programming languages.
Although machine language is difficult and complex for humans, it has a set of features, most notably:
It uses binary numbers (0, 1) that the computer understands and executes directly without the need for a translator.
It allows programmers to control computer hardware, such as the processor and memory, directly.
It is characterized by speed and efficiency compared to high-level languages.
Low-level languages are suitable for developing basic system programs for computers.
The most prominent disadvantages of machine language include:
It is difficult to write and execute complex programs and algorithms.
It requires many lines of code for a simple program.
Modifying programs requires a lot of time and effort.
It is difficult to identify and correct programming errors (debugging).
Machine language is hardware-specific, meaning it depends on the type or family of the processor it will run on, thus varying from one device to another.
In today's article, we learned about machine language, which sits at the lowest level of programming languages and is understood directly by the computer's CPU. We covered how to deal with it and its most prominent pros and cons. Today, machine language or low-level languages are no longer used by programmers except in very limited fields that require direct hardware interaction. High-level languages have become much easier, but remember that all programming languages are ultimately converted into machine language programs to be executed by the computer.