← All posts
6 min read

Source Code and Machine Code: How Human-Readable Programs Run

#technology#computer-science#compilers#programming
📑 On this page

Processors execute encoded machine instructions. Developers usually write text containing names, expressions, functions, and structures.

Tools connect those representations.

Source code is a human-oriented description in a programming language. Machine code is binary instruction data for a particular processor architecture.

Compilation and interpretation are broad strategies, and modern runtimes often combine them.

Source languages

A programming language defines:

  • Syntax
  • Value rules
  • Operations
  • Control flow
  • Error behavior
  • Libraries or runtime expectations

Examples include C, Rust, Java, Python, and JavaScript.

Source code is still data: a text file encoded into bytes. The language tool parses those characters according to grammar.

A syntax error means the tool cannot construct a valid program from the source.

Machine instructions

A processor instruction set defines operations and their binary encodings.

Machine instructions can:

  • Load data
  • Store data
  • Add values
  • Compare
  • Branch
  • Call routines

The same bytes can mean different instructions under different architectures.

An x86-64 executable does not directly run on an ARM processor unless software translates or emulates it.

Machine code also depends on operating-system executable formats and calling conventions.

Compilation

A compiler translates source into another representation before ordinary execution.

A simplified pipeline:

Compilation can detect:

  • Syntax errors
  • Type errors
  • Missing names
  • Invalid operations

It can also optimize the program while preserving defined behavior.

Object files and linking

Large programs compile modules separately into object files.

Object files contain:

  • Machine code
  • Data
  • Symbols
  • Relocation information

The linker combines them with libraries, resolves symbol references, and creates an executable or shared library.

If code calls a function whose implementation cannot be found, linking fails.

Dynamic linking can defer some library resolution until program load or runtime.

Interpreters

An interpreter executes source or an intermediate form through another program.

A simplified model:

  1. Read instruction or syntax structure.
  2. Determine meaning.
  3. Perform corresponding operation.
  4. Continue.

Interpretation offers flexibility and interactive behavior.

It can add runtime overhead because work is mediated by the interpreter.

The label "interpreted language" is imprecise: a language can have several implementations using compilation, interpretation, or both.

Bytecode and virtual machines

Some systems compile source into portable bytecode for a virtual machine.

Java commonly compiles source to JVM bytecode. .NET languages compile to an intermediate language.

The virtual machine:

  • Loads bytecode
  • Verifies it
  • Manages memory
  • Provides libraries
  • Interprets or compiles it for the current CPU

This enables the same intermediate program to run on platforms with compatible runtimes.

Just-in-time compilation

A JIT compiler produces machine code during execution.

It can observe actual behavior:

  • Frequently executed functions
  • Common value types
  • Typical branches

The runtime optimizes hot paths and may deoptimize if assumptions become false.

JavaScript engines and managed runtimes use sophisticated JIT techniques.

Startup includes parsing and compilation work, while long-running code can become highly optimized.

A concrete C example

Source:

int add(int left, int right) {
    return left + right;
}

A compiler:

  1. Parses the function.
  2. Checks parameter and result types.
  3. Produces an internal representation.
  4. Chooses registers and instructions.
  5. Emits object code for the target architecture.

The exact instructions depend on:

  • CPU target
  • Compiler
  • Optimization level
  • Calling convention

The source expresses intent; generated code implements it under platform rules.

Optimization

Compilers can:

  • Remove unreachable code
  • Inline functions
  • Simplify constants
  • Reorder safe operations
  • Vectorize loops
  • Allocate registers

Optimized code can be harder to debug because source steps no longer correspond one-to-one with instructions.

Undefined behavior in some languages lets compilers assume invalid situations never happen, producing surprising results when code violates those rules.

Optimization is constrained by the language specification, not by what a developer informally expected.

Debug and release builds

Debug builds often prioritize:

  • Symbol information
  • Easy source stepping
  • Runtime checks
  • Short compile time

Release builds prioritize:

  • Performance
  • Size
  • Optimization

A bug appearing only in release can involve timing, undefined behavior, uninitialized data, or optimization-sensitive assumptions.

Both configurations need testing.

Portability

Source portability depends on more than language syntax.

Code can rely on:

  • OS APIs
  • File paths
  • Byte order
  • Numeric sizes
  • Locale
  • Device features
  • Third-party libraries

A cross-platform language does not automatically make every program cross-platform.

Build systems select platform-specific code and dependencies.

Containers package user-space software but still depend on compatible host kernel and CPU architecture.

Decompilation

Machine code can be disassembled into assembly instructions.

Decompilers attempt to reconstruct higher-level structure, but original names, comments, formatting, and some abstractions are often gone.

Optimizations further change structure.

Compiled does not mean impossible to analyze. Protect secrets through secure design rather than assuming code cannot be inspected.

Common misunderstandings

"Processors execute source code"

They execute machine instructions; runtimes and tools translate or mediate source behavior.

"Compiled programs never need a runtime"

They can depend on operating-system loaders, shared libraries, garbage collectors, or language support.

"Interpreted code is always slow"

JIT compilation, optimized runtimes, libraries, and workload shape performance.

"One executable runs on every CPU"

Machine code targets a processor architecture and platform format unless translation is provided.

Knowledge check

1. What does a compiler broadly do?

It translates source code into another representation, often optimized object or machine code.

2. What does the linker do?

It combines compiled modules and libraries, resolves references, and produces a loadable result.

3. What is JIT compilation?

It generates machine code during program execution, often optimizing frequently used paths from observed behavior.

4. Why might one binary fail on another CPU architecture?

Its machine instructions and executable assumptions target a different instruction set or platform.

The one idea to remember

Source code expresses programs for humans and language tools; machine code encodes operations for a specific processor.

Compilers, interpreters, linkers, virtual machines, and JIT runtimes form different bridges between those layers.

Next, we will examine how variables name program values and how data types define what those values mean.