Grade by Grade
C. Computer Environments and Systems · Computer Environments and Systems

Source Code and Machine Code

C3.3About 65 minutes
Learning goals
  • I can explain the difference between source code and machine code.
  • I can describe high-level languages, assembly language and machine code, and how one is translated into the next.
  • I can explain why machine code depends on the processor type (and operating system) while source code can be portable.
  • I can convert simple pretend assembly instructions into binary machine code, by hand and with Python.

Words to know

source code
The human-readable instructions a programmer writes in a programming language, saved as a text file (such as a .py file).
machine code
Instructions in binary (1s and 0s) that a particular processor can execute directly.
instruction set
The complete list of machine instructions a processor type understands, such as x86-64 or ARM.
opcode
The part of a machine instruction that says which operation to do (load, add, store...).
operand
The part of a machine instruction that says what to work on: a register, a value or a memory address.
assembly language
A low-level language with a short word (mnemonic) for each machine instruction, such as ADD or MOV.
high-level language
A language close to human language and math, such as Python, Java or C++; one line can become many machine instructions.
bytecode
An in-between code, simpler than source code, that a virtual machine runs. Python and Java both use bytecode.
executable
A file of machine code that the operating system can run, such as a .exe file on Windows.

Two versions of every program

Every program you run exists in two forms. The one you write in your IDE is source code: words, symbols, names like total, comments and indentation. The one the CPU runs is machine code: long strings of 1s and 0s.

In the lesson How a Program Runs on the Hardware, you watched a pretend CPU carry out instructions like LOAD and ADD. This lesson connects that picture to the code you write.

Source code

Source code is written by people, for people (and for the translator). It:

  • is plain text, written in a programming language such as Python, Java or C++, and saved in files such as game.py;
  • uses meaningful names (total, average), comments and indentation so humans can read it;
  • can be read, edited, shared and printed;
  • cannot be run directly by the CPU. It must first be translated.

High-level languages like Python are designed for people: one line such as total = price * qty says a lot. Most high-level source code is portable: the same Python file runs on Windows, macOS and Linux.

Machine code

Machine code is what the CPU actually executes:

  • Each instruction is a binary number. Part of it is the opcode (which operation: load, add, compare, jump...) and the rest are operands (which registers, values or memory addresses).
  • It is specific to the processor type: an x86-64 CPU and an ARM CPU have different instruction sets, so the same 1s and 0s mean different things to them. Executable files also depend on the operating system, so a Windows .exe will not run on macOS.
  • Names, comments and indentation are gone. It is extremely hard for people to read.

Between the two is assembly language: one short, readable word (mnemonic) per machine instruction. A program called an assembler turns it into machine code. For example, on an x86-64 processor, the assembly instruction add eax, ebx (add register ebx to register eax) becomes the two bytes 00000001 11011000.

Source code (Python)total = a + bfor people: names, commentsAssembly languageLOAD 6ADD 7STORE 8one line per CPU instructionMachine code000101100010011100111000binary: what the CPU runsEasier for people ←————————————————→ what the hardware understands
The same calculation at three levels, using the pretend CPU from the lesson How a Program Runs on the Hardware, with a at address 6, b at address 7 and total at address 8. Each machine instruction is 8 bits: a 4-bit opcode followed by a 4-bit address.
Source codeMachine code
Written byprogrammersa compiler or assembler (translated from source)
Looks likewords, symbols, names, commentsbinary numbers (often shown in hexadecimal)
Readable by people?yes, by designbarely
Runs on the CPU directly?no, must be translatedyes
Depends on processor type?usually not (portable)yes (x86-64 and ARM differ)
Typical filegame.py, game.c, Game.javagame.exe (Windows), an app bundle (macOS)
Comparing source code and machine code.

A pretend assembler

Our pretend CPU uses 8-bit instructions. The first 4 bits are the opcode (LOAD = 0001, ADD = 0010, STORE = 0011, PRINT = 0100, HALT = 1111) and the last 4 bits are the address (0 to 15). The program below is an assembler: it translates assembly into machine code. format(n, "04b") writes a number in binary with 4 digits. (Binary numbers are covered in the Programming Basics unit.)

names = ["LOAD", "ADD", "STORE", "PRINT", "HALT"]
codes = [1, 2, 3, 4, 15]
assembly = [["LOAD", 6], ["ADD", 7], ["STORE", 8], ["PRINT", 8], ["HALT", 0]]
for op, addr in assembly:
opcode = codes[names.index(op)]
machine = format(opcode, "04b") + format(addr, "04b")
print(op.ljust(6), addr, " -> ", machine)

Each assembly line becomes exactly one machine instruction. Output (one line each): LOAD 6 -> 00010110 / ADD 7 -> 00100111 / STORE 8 -> 00111000 / PRINT 8 -> 01001000 / HALT 0 -> 11110000

game.cone source filecompiler forx86-64 + Windowscompiler forARM + macOSgame.exex86-64 machine codegame (app)ARM machine codedifferent 1s and 0s:not interchangeable
One source file can be compiled for different processors and operating systems, giving different machine code. That is why software downloads often ask "Windows or Mac?" and "Intel or Apple silicon (ARM)?".

Where does Python fit?

Python is a little different. When you run a .py file, Python first translates your source code into bytecode: simple instructions for a pretend computer called the Python virtual machine. The Python interpreter (which is itself machine code for your CPU) then carries out the bytecode. You can peek at bytecode with Python's dis module. For total = price * qty, Python 3.13 shows instructions named LOAD_NAME price, LOAD_NAME qty, BINARY_OP (multiply), STORE_NAME total (other versions differ slightly). Notice how much they look like our pretend CPU! You will compare Python with compiled languages in the next lesson, Interpreters and Compilers.

code = compile("total = price * qty", "<demo>", "exec")
print(code.co_names)
print(type(code.co_code))

compile() turns source code into a code object without running it. Its bytecode (co_code) is raw bytes, and it keeps a table of the names it uses. Output (one line each): ('price', 'qty', 'total') / <class 'bytes'>

Worked example

Example 1: Assemble by hand

Using the pretend instruction set, write the machine code for: LOAD 9, ADD 10, STORE 11, HALT.

  1. LOAD is 0001 and 9 in 4-bit binary is 1001 (8 + 1): 0001 1001.
  2. ADD is 0010 and 10 is 1010 (8 + 2): 0010 1010.
  3. STORE is 0011 and 11 is 1011 (8 + 2 + 1): 0011 1011.
  4. HALT is 1111 and the address is unused (0000): 1111 0000.

00011001, 00101010, 00111011, 11110000

Worked example

Example 2: Disassemble (machine code back to assembly)

A pretend-CPU program in memory is: 00010111 00100110 01000111 11110000. What does it do?

  1. Split each byte into opcode (first 4 bits) and address (last 4 bits).
  2. 0001 0111: opcode 0001 = LOAD, address 0111 = 7 → LOAD 7.
  3. 0010 0110: ADD 6 → ADD 6. 0100 0111: PRINT 7 → PRINT 7. 1111 0000: HALT.
  4. Careful: it adds ram[7] and ram[6] into the accumulator, but never stores the result, so PRINT 7 prints the old value at address 7. Reading machine code is hard, and bugs like this are hard to spot, which is why people write source code instead.

LOAD 7, ADD 6, PRINT 7, HALT: it prints the original value at address 7 (the sum is never stored).

Just for fun

In the 1960s and 1970s, many programs were stored as stacks of punched cards, one line of code per card. Dropping a box of cards meant hours of re-sorting, so some programmers drew a diagonal stripe across the top edge of the deck: if it fell, the stripe showed the order again.

Hands-on activity

Build and test your own assembler

You need: a computer with Python.

  1. Run the assembler program. Check each output against the opcode table by hand.
  2. Add a new instruction SUB with opcode 0101 (add it to both lists, in the same position). Assemble LOAD 7, SUB 6, STORE 8, PRINT 8, HALT. Write the machine code you expect before running.
  3. Write a disassembler: a loop that takes a list of 8-character binary strings, splits each into bits[:4] and bits[4:], converts with int(part, 2), and prints the instruction name. Test it on Example 2.
  4. Explain in two or three sentences why a real program's machine code cannot simply be copied from a Windows PC to an ARM-based Mac and run.

What to look for: step 2 gives 00010111, 01010110, 00111000, 01001000, 11110000; step 3 prints LOAD 7, ADD 6, PRINT 7, HALT; step 4 mentions different instruction sets (and operating systems), so the same bits mean different things, and the program must be recompiled from source or run through a translation layer (Apple's Rosetta 2 is one example).

Your turn

1

What is source code?

2

What is machine code?

3

Why can't the CPU run Python source code directly?

4

What are the two parts of a machine instruction?

5

Using the pretend instruction set, what is the machine code for STORE 5?

6

Why does a download page ask whether you have Windows or macOS?

7

Is assembly language source code or machine code?

More practice: warm-up

1

Name two processor instruction sets.

2

What file extension does Python source code use?

3

What translates assembly language into machine code?

4

Do comments appear in machine code?

5

What is 1010 in binary as a decimal number?

More practice: on your own

1

Disassemble 00010110 00100110 00111001 11110000 using the pretend instruction set. What does the program compute?

2

Explain why source code is said to be portable but machine code is not.

3

Why do companies that sell software often distribute only machine code, not source code?

4

What does format(13, "04b") return? What about format(3, "04b")?

5

In Python, compile("x = a + b", "<demo>", "exec").co_names returns what?

Challenge

1

The pretend CPU has 4-bit addresses. How many memory locations can it address, and how many different opcodes are possible? Why do real CPUs use longer instructions?

2

Explain how bytecode makes Python programs portable, even though the Python interpreter itself is machine code.

3

A student says, "If I have the machine code, I can always get back the original source code." Is that true? Explain.

Check your understanding

Quiz

1.

Which statement about source code is true?

2.

Machine code is...

3.

Using the pretend instruction set (ADD = 0010), what is the machine code for ADD 3?

4.

Why won't a Windows .exe file run directly on a Mac with an ARM processor?

5.

Assembly language is translated into machine code by...

6.

What does Python translate your source code into before running it?

For parents and teachers

Big idea: Expectation C3.3 asks students to explain the difference between source code and machine code. The pretend assembler makes the idea concrete: source code is for people; machine code is for one kind of processor.

How to help: Ask your teen to explain why a program downloaded for Windows will not run on a Mac, or why an app store has different versions for different devices. Have them disassemble one of the binary examples for you.

Common difficulties:

  • Thinking the CPU "reads Python." It runs machine code; Python is translated first.
  • Believing machine code is the same everywhere.
  • Mixing up assembly (readable mnemonics) and machine code (binary).
  • Binary conversions: each 4-bit group uses place values 8, 4, 2, 1.