Evolution of Programming Languages

Machine Code, Assembly Languages, and High-Level Languages


Learning Objectives

  • You know the early history of programming languages, starting with machine code and assembly languages, and know why high-level languages were developed.
  • You understand that even though programming languages have evolved, the underlying principles of computers are still the same.

Early Computer Programming

The first programmable computers like ENIAC were programmed physically by setting switches and plugging cables. Taking a problem, breaking it down into simple steps, and mapping those steps to the computer’s hardware was a manual and time-consuming process.

The introduction of stored-program computers like the EDVAC and Manchester Baby marked a significant change in programming. These machines could store programs to memory, and execute them from there, making programming more flexible and efficient.

However, the programming process was still very low-level, involving direct manipulation of memory addresses and registers. Programmers had to work directly with the native machine code, which consists of binary digits (0s and 1s), with each instruction corresponding to a specific operation in the processor. For example, an instruction to load a value into a register or to perform an arithmetic operation is represented by a unique sequence of bits. Such level of abstraction required a deep understanding of computer architecture, as even simple operations required a precise sequence of binary instructions.

As an example, consider the following hypothetical snippet of machine code:

10110000 01100001

The first byte (10110000) might represent an opcode instructing the CPU to load data, while the second byte (01100001) could specify the memory address or register involved in the operation. The direct control over hardware offered power, but required thinking in terms of hardware states, memory addresses, and binary arithmetic.

Machine code programmers had to manually translate their ideas for algorithms into the binary sequence, which was both time-consuming and error-prone. A small error in a single bit could lead to unintended behavior or system crashes. Despite the challenges — or maybe because of the challenges — the early work in machine code established a foundation for advances in programming.

There also were no spaces or line breaks, as everything was entered in binary. The above example is a simplification for illustration purposes.

Loading Exercise...

Emergence of Assembly Languages

As programming evolved, the need for a more human-readable and manageable form of code led to the development of assembly languages. Assembly languages serve as a thin abstraction over machine code, replacing cumbersome binary sequences with codes and symbolic names that are easier to remember than the corresponding binary values. For instance, rather than writing an instruction in binary, a programmer could write:

MOV AL, 05h   ; Load the hexadecimal value 05 into register AL
ADD AL, 03h   ; Add the hexadecimal value 03 to register AL

In this snippet, MOV and ADD are codes — mnemonics — that represent machine-level operations. The codes makes the program significantly easier to read, write, and maintain. Moreover, the symbolic representation of registers and memory addresses (like AL) further helps reduce the cognitive load.

Lack of Abstractions and Portability

While machine code and assembly provided control over computer hardware, they had limitations. One of the main challenges was the complexity of programming. Every operation, no matter how simple, required a detailed sequence of instructions. For example, consider the following assembly snippet that performs a basic arithmetic operation:

; Adding two numbers using assembly language
MOV AX, 0001h   ; Load the value 1 into register AX
ADD AX, 0002h   ; Add the value 2 to register AX
JMP 0xFF        ; Unconditional jump that can lead to unpredictable behavior if not managed carefully

The explicit management of registers, memory addresses, and jumps (like the unconditional jump shown above) illustrates how easily a small mistake could result in a misbehaving program. Debugging low-level code is challenging because the programmer must track each operation.

Another limitation was portability. Because machine code and assembly instructions are tied to the hardware, code written for one system did not automatically work on another.

Loading Exercise...

Towards High-Level Languages

The issues of low-level programming led to the development of high-level languages. The first widely adopted high-level language is often considered to be Fortran (short for “Formula Translation”), developed by IBM in the late 1950s.

Fortran was designed for scientific and engineering computations, allowing developers to write instructions in a form that was much closer to human language or mathematical notation, abstracting away many of the low-level details associated with machine code and assembly language. For example, a simple Fortran program to print a message might look like this:

PRINT *, 'Hello, World!'
END

In contrast to low-level code, the high-level code is concise, clear, and easy to understand. The programmer no longer needed to manage registers or memory addresses directly; instead, they could express their intent more efficiently and in a less error-prone manner.

There were a range of high-level languages developed before and after Fortran, each with its own strengths and focus. For example, Lisp was designed for symbolic computation, while COBOL was developed for business data processing. These languages introduced features like functions, loops, and data structures that made programming more accessible and efficient.

COBOL programs were also designed to be self-documenting. The following COBOL program prints “Hello, World!”:

IDENTIFICATION DIVISION.
PROGRAM-ID. HELLO.
PROCEDURE DIVISION.
    DISPLAY 'Hello, World!'.
    STOP RUN.

When considering the shift from programming by physically setting switches to writing high-level code, the benefits of higher-level languages are clear. High-level languages abstract away the complexities of the underlying hardware, allowing programmers to focus on solving problems rather than managing machine details.

At the same time, the underlying principles of computers are still same, and the high-level code is translated into machine code that the computer can execute.

Loading Exercise...