You are viewing a free preview of this lesson.
Subscribe to unlock all 10 lessons in this course and every other course on LearningBro.
This lesson classifies programming languages by level (machine code, assembly, and high-level imperative), explains why different languages exist, develops the modes of addressing memory (immediate, direct, indirect and indexed) with worked examples, and introduces the Little Man Computer (LMC) as a model of how a simple processor executes a program.
This lesson develops OCR H446 section 1.2.4 (Types of Programming Language). It requires you to know the levels and types of language and the reasons different languages exist, the modes of addressing memory, and to understand the Little Man Computer as a teaching model of the stored-program machine. Programming paradigms (procedural, object-oriented, functional and declarative as styles of programming) and detailed language mechanics belong to the separate Programming course and are only cross-referenced here — this lesson owns the levels/types, the addressing modes and the LMC. The addressing-mode material connects directly to assembly language and the CPU's fetch–decode–execute cycle from the architecture topic.
Programming languages exist on a spectrum from low-level (close to hardware) to high-level (close to human language). "Level" measures the distance from the hardware: the lower the level, the more directly the language maps onto what the CPU physically does, and the more the programmer must manage registers and memory by hand.
flowchart TB
HL["Human Language"]
HLL["High-Level Languages (Python, Java, C#, JavaScript)"]
ASM["Assembly Language"]
MC["Machine Code"]
HW["Hardware (CPU)"]
HL --> HLL --> ASM --> MC --> HW
A common exam question asks why there are so many languages rather than one. The honest answer is that languages are tools shaped by competing pressures, and no single design wins on every axis:
Keep this list in mind — when a question gives a scenario ("a team building a control system for a satellite", "a data scientist exploring a dataset"), you justify the language choice by appealing to whichever of these pressures dominates.
Machine code is the lowest-level programming language — it consists of binary instructions (sequences of 0s and 1s) that the CPU can execute directly.
| Feature | Detail |
|---|---|
| Format | Binary (e.g. 0011 0001 0000 1010) |
| Readability | Virtually unreadable to humans |
| Portability | Specific to a particular CPU architecture — code written for an x86 processor will not run on ARM |
| Execution | Executed directly by the CPU with no translation needed |
| Speed | Fastest possible — no translation overhead |
| Use today | Almost never written by hand; generated by compilers and assemblers |
Each machine code instruction consists of an opcode (the operation) and an operand (the data or address).
Assembly language is a low-level language that uses mnemonics (short, human-readable abbreviations) to represent machine code instructions.
| Feature | Detail |
|---|---|
| Format | Mnemonics (e.g. LDA 50, ADD R1 R2, MOV AX BX) |
| Readability | Much more readable than binary, but still requires knowledge of the specific processor |
| Portability | Not portable — tied to a specific CPU architecture. ARM assembly is different from x86 assembly |
| Translation | Must be translated into machine code by an assembler before execution |
| Relationship to machine code | One-to-one — each assembly instruction corresponds to exactly one machine code instruction |
Each assembly instruction is one opcode (the operation) plus an operand (the data or an address), and it maps to exactly one machine-code instruction. A short routine that loads 5, adds 3, and stores the result at address 100 looks like this in assembly:
LDA #5 ; load the immediate value 5 into the accumulator
ADD #3 ; add the immediate value 3 (accumulator now holds 8)
STA 100 ; store the accumulator (8) into memory location 100
Assembled to machine code (with an illustrative 4-bit opcode and 12-bit operand) each line becomes a single binary instruction:
| Assembly | Opcode | Operand | Machine code (binary) |
|---|---|---|---|
LDA #5 | 0001 | 0000 0000 0101 | 0001 0000 0000 0101 |
ADD #3 | 0010 | 0000 0000 0011 | 0010 0000 0000 0011 |
STA 100 | 0011 | 0000 0110 0100 | 0011 0000 0110 0100 |
The one-to-one correspondence is what makes an assembler simple: it largely substitutes each mnemonic for its opcode and each label for its address. Contrast this with a high-level statement such as total = a + b + c, which a compiler must expand into several machine instructions (multiple loads, additions and a store).
| Use Case | Why Assembly? |
|---|---|
| Device drivers | Need direct hardware access and precise control over timing |
| Embedded systems | Tight memory and performance constraints; every byte and cycle matters |
| Operating system kernels | Low-level hardware interaction (e.g. interrupt handlers, context switching) |
| Real-time systems | Predictable execution time is critical |
| Reverse engineering | Analysing compiled programs |
| Performance-critical code | Inner loops in games or scientific simulations where every nanosecond counts |
High-level languages use syntax that is closer to human language and mathematical notation. They abstract away the details of the hardware.
| Feature | Detail |
|---|---|
| Format | English-like syntax (e.g. if, while, for, print, function) |
| Readability | Easy to read and write for humans |
| Portability | Portable — the same source code can run on different hardware if a suitable compiler/interpreter is available |
| Translation | Must be translated into machine code by a compiler or interpreter |
| Relationship to machine code | One-to-many — one high-level statement typically translates to multiple machine code instructions |
| Abstraction | Programmer does not need to know about registers, memory addresses or CPU-specific instructions |
| Language | Typical Use | Paradigm |
|---|---|---|
| Python | Web development, data science, scripting, education | Object-oriented, procedural, functional |
| Java | Enterprise software, Android apps, web services | Object-oriented |
| C | System programming, embedded systems, OS development | Procedural |
| C++ | Games, system software, performance-critical applications | Object-oriented, procedural |
| JavaScript | Web front-end and back-end, mobile apps | Object-oriented, functional, event-driven |
| C# | Windows applications, games (Unity), enterprise | Object-oriented |
| Feature | Low-Level (Machine Code / Assembly) | High-Level |
|---|---|---|
| Readability | Very low (binary) / Low (mnemonics) | High (English-like) |
| Portability | Not portable — CPU-specific | Portable — hardware-independent |
| Abstraction | Minimal — programmer manages hardware directly | High — hardware details hidden |
| Speed of execution | Fastest (direct hardware) | Slightly slower (translation overhead, less optimised) |
| Speed of development | Very slow — tedious, error-prone | Fast — expressive syntax, built-in libraries |
| Debugging | Very difficult | Easier — meaningful variable names, error messages |
| Memory control | Full control over every byte and register | Managed by the language/runtime |
| Code length | Longer (one instruction per operation) | Shorter (one statement = many operations) |
| Typical use | Drivers, embedded, OS kernels | Applications, web, data science, games |
| Generation | Name | Examples | Characteristics |
|---|---|---|---|
| 1GL | Machine code | Binary instructions | Directly executable by CPU |
| 2GL | Assembly language | ARM assembly, x86 assembly | Mnemonics; one-to-one with machine code |
| 3GL | High-level languages | Python, Java, C++, C# | Hardware-independent; one-to-many translation |
| 4GL | Very high-level / domain-specific | SQL, MATLAB, R | Designed for specific problem domains |
| 5GL | Natural language / AI | Prolog (logic programming) | Constraint-based; the programmer specifies what to solve, not how |
Exam Tip: When comparing low-level and high-level languages, always give at least four specific differences and provide examples. Include both advantages and disadvantages of each. Mention that the choice depends on the specific requirements of the project.
Note on paradigms: 4GL and 5GL hint at declarative and logic styles. These programming paradigms — procedural, object-oriented, functional and declarative as ways of structuring a solution — are developed in the separate Programming course. Here, keep the focus on levels of language; reach for the paradigms material when a question asks about programming style rather than closeness to hardware.
When a low-level instruction names an operand, the addressing mode tells the CPU how to interpret that operand — is it a literal value, a memory address, an address that points to another address, or an address to be adjusted by an index? The same bit pattern in the operand field can mean very different things depending on the mode. OCR requires four modes: immediate, direct, indirect and indexed.
Throughout the examples below, assume this small slice of memory and one index register (IR):
| Address | Contents |
|---|---|
| 20 | 64 |
| 30 | 20 |
| 40 | 99 |
| 41 | 88 |
| 42 | 77 |
Index register IR = 2, and the instruction we are explaining is conceptually LDA <operand> ("load the accumulator").
The operand is the value itself — no memory lookup. This is the fastest mode because the data travels inside the instruction.
LDA #20 ; immediate: load the literal value 20
Result: the accumulator holds 20. The # (or equivalent notation) marks the operand as a literal. Use immediate addressing for constants known at compile time.
The operand is a memory address; the CPU loads the contents of that address.
LDA 20 ; direct: load the contents of address 20
Result: address 20 holds 64, so the accumulator holds 64. This is the everyday mode for accessing a named variable. Its limitation is range — the operand field has a fixed number of bits, so direct addressing can only reach addresses that fit in that field.
The operand is a memory address that holds another address; the CPU follows the pointer — it loads the contents of the address stored at the operand address.
LDA (30) ; indirect: address 30 holds 20, so load the contents of address 20
Result: address 30 holds 20, and address 20 holds 64, so the accumulator holds 64. Indirect addressing needs two memory accesses (fetch the pointer, then fetch the data), so it is slower — but it lets the operand field "point beyond its own range" and is the natural mechanism behind pointers and dynamically chosen targets.
The effective address is the operand plus the contents of an index register. The CPU adds the index register to the operand to compute where to read.
LDA 40,IR ; indexed: effective address = 40 + IR
With IR = 2 the effective address is 40+2=42, and address 42 holds 77, so the accumulator holds 77. Indexed addressing is built for arrays and loops: keep the base address (40) fixed in the operand and increment the index register to step through consecutive elements, which is exactly how a high-level for loop over an array is implemented at the machine level.
| Mode | Operand means | Effective data fetched | Memory accesses to get data | Typical use |
|---|---|---|---|---|
| Immediate | The literal value | The operand itself | 0 | Constants |
| Direct | An address | Contents of that address | 1 | Simple variables |
| Indirect | An address of an address | Contents of the pointed-to address | 2 | Pointers, indirection |
| Indexed | A base address (+ index reg) | Contents of (operand + index) | 1 (plus the register read) | Arrays, loops |
Working the same operand through every mode is the classic exam exercise — make sure you can state both the effective address and the final value loaded for each, and explain why indirect is slower (an extra memory access) while immediate is fastest (none).
The Little Man Computer is a simplified model of a von Neumann stored-program machine, used to make the fetch–decode–execute cycle concrete without the complexity of a real CPU. Imagine a little man working in a mailroom of 100 numbered boxes (mailboxes 00–99, the memory), with:
flowchart TB
PC["Program counter"] --> MEM["Mailboxes 00-99<br/>(memory: instructions + data)"]
MEM --> ALU["Little man + accumulator<br/>(decode & execute)"]
IN["In-tray (input)"] --> ALU
ALU --> OUT["Out-tray (output)"]
ALU --> MEM
Subscribe to continue reading
Get full access to this lesson and all 10 lessons in this course.