All computers at bottom consist of circuits that can perform a repertoire of mathematical or logical operations. The earliest computers were programmed by setting switches for operations and manually entering numbers in working storage, or memory. A major advance in the flexibility of computers came with the idea of stored programs, where a set of instructions could be read in and held in the machine in the same way as other data. These instructions were in machine language, consisting of numbers representing instructions (operations to be performed) and other numbers representing the address of data to be manipulated (or an address
containing the address of the data, called indirect addressing—see addRessing). Operations include basic arithmetic (such as addition), the movement of data between storage (memory) and special processor locations called registers,
and the movement of data from an input device (such as a card reader) and an output device (such as a printer).
Writing programs in machine code is obviously a tedious and error-prone process, since each operation must be specified using a particular numeric instruction code
together with the actual addresses of the data to be used. It soon became clear, however, that the computer could itself be used to keep track of binary codes and actual addresses, allowing the programmer to use more human-friendly names for instructions and data variables. The program that translates between symbolic language and machine language is the assembler.
With a symbolic assembler, the programmer can give names to data locations. Thus, instead of saying (and having to remember) that the quantity Total will be in location &H100, the program can simply define a two-byte chunk of memory and call it Total:
The assembler will take care of assigning a physical memory location and, when instructed, retrieving or storing the data in it.most assemblers also have macro capability. This means that the programmer can write a set of instructions (a pro-
cedure) and give it a name. Whenever that name is used in the program, the assembler will replace it with the actual code for the procedure and plug in whatever variables are specified as operands (see macRo).
In the mainframe world of the 1950s, the development of assembly languages represented an important first step toward symbolic programming; higher-level languages such as FORTRAN and COBOL were developed so that programmers could express instructions in language that was more like mathematics and English respectively. High-level languages offered greater ease of programming and source
code that was easier to understand (and thus to maintain). gradually, assembly language was reserved for systems programming and other situations where efficiency or the need to access some particular hardware capability required the exact specification of processing (see systems pRogRamming and device dRiveR).
During the 1970s and early 1980s, the same evolution took place in microcomputing. The first microcomputers typically had only a small amount of memory (perhaps
8–64K), not enough to compile significant programs in a high-level language (with the partial exception of some versions of BASIC). Applications such as graphics and games in particular were written in assembly language for speed. As available memory soared into the hundreds of kilobytes and then megabytes, however, high level languages such as C and C++ became practicable, and assembly language began
to be relegated to systems programming, including device drivers and other programs that had to interact directly with the hardware.
While many people learning programming today receive little or no exposure to assembly language, some understanding of this detailed level of programming is still useful because it illustrates fundamentals of computer architecture and operation.