TutorChase logo
Login
AQA A-Level Computer Science

15.2.2 Low-Level Languages: Machine Code

Machine code is the most fundamental language a computer understands, consisting solely of binary digits. It allows direct interaction with a processor’s hardware and underpins all software execution.

What is machine code?

Machine code is the lowest-level programming language and is made up entirely of binary digits (0s and 1s). Every instruction written in machine code is designed to be directly executed by the central processing unit (CPU) without any further translation. It is the native language of a computer system, and all higher-level programming languages must ultimately be compiled or interpreted into machine code before they can be run on a processor.

A machine code program is composed of a sequence of binary instructions, where each instruction performs a very specific, small task. These tasks may include loading data into a register, performing arithmetic, storing data in memory, or changing the flow of execution via jumps or branches.

Binary structure

Each machine code instruction typically consists of:

  • An opcode (operation code), which tells the CPU what operation to perform.

  • One or more operands, which provide the data to be used or reference locations (such as memory addresses or registers).

For example:

10110000 01100001

This binary instruction might tell the processor to move the value 97 (01100001 in binary) into a register. However, the exact meaning depends on the Instruction Set Architecture (ISA) of the specific CPU.

Take your grades to the next level!

UPGRADING TO PREMIUM UNLOCKS
AI Tutor
AI-powered study assistant
instant feedback and guidance
Predicted Papers
Examiner-style predicted papers
based on recent exam trends
Practice Questions
All exam practice questions
by topic for each subject
Study Notes
All detailed revision notes
written by expert teachers
Cheat Sheets
Quick revision summaries
perfect for last-minute review
Past Papers
Complete collection
of practice and past exam papers
Email
Password
Confirm Password
Already have an account?

Practice Questions

FAQ

Machine code is composed entirely of binary digits—strings of 0s and 1s—which provide no contextual clues or readable syntax for humans. Unlike assembly language, which uses mnemonics like MOV, ADD, and symbolic labels to represent operations and memory references, machine code lacks any meaningful symbols. Each instruction must be decoded using a processor’s instruction set documentation to determine its function. Without tools such as disassemblers or hex editors, even skilled programmers struggle to identify what a binary sequence represents. Furthermore, errors in binary instructions are extremely hard to detect, since a single flipped bit can change the meaning of an instruction completely or produce undefined behaviour. This makes writing, debugging, and modifying machine code highly error-prone and time-consuming. Assembly language, while still low-level and hardware-specific, provides a symbolic bridge between human-readable commands and machine-executable binary, making it far more manageable and interpretable during software development and reverse engineering.

In machine code, addressing modes determine how the operand in an instruction is interpreted. Absolute addressing means the operand specifies an exact memory location. For example, an instruction with an operand of 1000 will always access memory location 1000, regardless of the current execution context. This provides straightforward, predictable access to memory but can make code inflexible, especially when programs are moved in memory or executed on systems with different memory layouts.
On the other hand, relative addressing uses the operand as an offset from the current position of the program counter (PC). For instance, if the current PC is 200 and the instruction has a relative offset of +5, the next instruction will jump to address 205. This makes relative addressing ideal for implementing loops, conditional branches, or jump instructions that don’t rely on fixed memory locations. Relative addressing is particularly useful in modern systems and bootloaders because it allows for relocatable code, making machine-level programs more adaptable and easier to manage during linking or loading.

Machine code does not inherently distinguish between numbers, characters, or instructions; all data and operations are stored as binary. The interpretation of a binary sequence entirely depends on context. For example, the same binary pattern 01000001 might represent:

  • An instruction (e.g. opcode to load a value)

  • A character ('A' in ASCII)

  • A number (decimal 65)

The processor treats a binary sequence as an instruction only when it is in the instruction fetch phase of the cycle and reading from a code segment in memory. During the execute phase, if the operand refers to a memory address, the CPU might then interpret that data as a number to use in arithmetic or as a pointer to another instruction.
Humans or compilers provide the semantic meaning during the programming phase. For example, a compiler knows when a particular data value should be stored as text, a number, or part of an instruction stream, and organises memory accordingly. This ambiguity is one reason why machine code programming is so difficult—it requires careful memory layout and an understanding of the intended use for each binary value.

Machine code handles conditional logic using branching instructions, typically based on the status flags set by previous operations. Most CPUs include a status register that stores bits indicating outcomes of recent computations, such as:

  • Zero flag (Z): Set if the result of an operation is zero.

  • Carry flag (C): Indicates overflow or carry from an arithmetic operation.

  • Negative flag (N): Indicates a negative result in signed operations.

  • Overflow flag (V): Set if the result exceeds the maximum representable value.

A comparison operation, such as subtracting one value from another, affects these flags. A conditional jump instruction then checks these flags and decides whether to alter the program’s control flow. For example:

  • JZ 0500 might jump to address 0500 if the zero flag is set.

  • JNZ 0600 might jump if it is not set.

These jumps enable the implementation of if statements, loops, and switch-case logic in higher-level languages. The logic is rigid and must be implemented manually with carefully constructed flag checks, making conditional execution in machine code less readable and more complex than in higher-level equivalents. However, it provides complete control over the decision-making process and is highly efficient in terms of performance.

Direct interaction with machine code is rare in general-purpose application development but still crucial in specific areas of computing. Embedded systems development often involves working with machine code or assembly to ensure efficient and predictable control over hardware with limited resources. For instance, when programming microcontrollers for automotive systems, robotics, or home appliances, developers may examine and fine-tune machine code to optimise speed and reduce memory usage.
Reverse engineering and security research are also common fields where professionals must interpret machine code to analyse malware, patch binaries, or understand proprietary software without source code. In these contexts, understanding binary instruction sequences is essential.
Additionally, compiler developers and those working on operating systems or bootloaders may work with raw machine code to debug the lowest layers of software execution, ensuring correctness before higher-level components take over. Even in cases where machine code is not written directly, understanding it is necessary to analyse how high-level code translates to hardware behaviour, performance bottlenecks, or instruction-level security flaws like buffer overflows.

Hire a tutor

Please fill out the form and we'll find a tutor for you.

1/2
Your details
Alternatively contact us via
WhatsApp, Phone Call, or Email