4.6.3.1 - Compilers, interpreters and assembly
A processor cannot directly execute arbitrary source text. Learn how assemblers, compilers and interpreters bridge that gap, and why some systems translate through bytecode rather than directly to a target machine.
Source code and object code
The notation a programmer edits and the instructions a processor executes have different roles.
Source code and object code
Source code is a program written in a source language, such as assembly or a high-level language. Object code is the translated output, usually machine code for a target processor, from which an executable program can be produced.
Source code may contain readable names, comments and constructs such as repetition. Machine code encodes instructions from a particular processor's instruction set. A translator does not change the intended task; it expresses that task in a representation the next stage can use.
In a simple account, a compiler produces object/executable code that can be run. In a real toolchain, object files may first need linking: combining the program's translated code with other object modules or required library code and resolving references between them. Loading places an executable in memory for execution. Thus an individual object file is not necessarily a complete directly runnable application. The important distinction is source before translation versus translated output, rather than a file extension or whether an icon can be double-clicked.
Changing the source does not automatically alter a previously built executable. If a developer repairs a calculation but runs an old build, the old translated instructions still contain the earlier behaviour. Rebuilding the relevant code and using the new executable is necessary.
Choosing assembly, compilation or interpretation
Choose a translation method by considering the source language and how the program will be developed and executed.
An assembler translates assembly source, with its mnemonics and operands, into machine code for the intended instruction set. It is appropriate when a programmer has written a processor-specific assembly routine. It does not turn an arbitrary high-level program into assembly merely because its name sounds like “putting code together”.
A compiler translates a source program into another representation before the resulting program is executed. Its output may be target machine/object code or an intermediate representation. A compiler checks source during translation and reports detected errors; a successful build does not prove that the algorithm is correct. When the output is a native executable, repeated runs do not require the source to be translated again each time. This can make compiled distribution and repeated execution efficient, although the executable usually targets a particular architecture and environment.
An interpreter carries out a program through a run-time translation/execution process. In the usual introductory model, it handles source statements as execution reaches them rather than first producing a separate native executable for the whole program. It can help interactive development: a programmer tries a change and receives an immediate result or diagnostic from the running environment. A run-time error encountered on an executed path may stop that run, while an error on a path not taken may remain undiscovered. This does not mean every interpreter ignores all syntax until reaching it; implementations can parse or check source first.
| Situation | Suitable approach and reason |
|---|---|
| A low-level routine written for one processor's registers and instructions | Assembly through a matching assembler preserves the chosen low-level operations |
| A repeatedly run application distributed as a native executable | Compilation performs translation ahead of those runs and allows distribution of the executable |
| Rapidly trying small changes in an interactive development environment | Interpretation supports prompt feedback without a separate native build step for every experiment |
Interpretation may have repeated run-time work and require the relevant interpreter/runtime on the user's machine. Compilation adds a build stage and can make platform-specific distribution necessary. Neither guarantees correct logic or a universal speed advantage. Modern implementations often combine compilation and interpretation, so these terms describe processes rather than mutually exclusive permanent labels attached to a language.
Intermediate code and subsequent execution
A compiler can target a virtual execution system rather than one physical processor's machine instructions.
Bytecode is an intermediate instruction representation designed for a virtual machine or run-time system. A source compiler can produce bytecode once. A compatible implementation of the virtual machine then executes those instructions on a particular host platform. The bytecode itself is not automatically the native machine code understood by every processor.
The subsequent stage may interpret the bytecode, translating and carrying out its instructions through the virtual machine. Alternatively, a just-in-time (JIT) compiler can translate bytecode, often selected frequently used portions, into native instructions during execution. The host-specific runtime bridges the remaining gap to physical hardware. These are alternative or combined implementation strategies, not two extra steps that every bytecode system must always perform.
The portability benefit comes from a common intermediate target: the same suitable bytecode can run on different hosts with compatible virtual-machine implementations, instead of the developer supplying a different native build for each processor. Compatibility still matters, including runtime version, libraries and any platform-specific facilities. A virtual machine is software that must itself be implemented for its host.
Producing bytecode also separates source-language translation from host execution. Multiple source languages can, where tools support it, target the same intermediate system. Translation and run-time overhead may remain, and portability is not a promise of identical performance on all hardware.
Source → bytecode → compatible virtual machine/runtime → host execution. Bytecode provides an intermediate target; the runtime supplies the host-specific execution mechanism.