When computer scientists first built programmable machines, they programmed them directly in machine code, which is a series of numbers that instructed the computer what to do. Writing machine language was very hard to do and took a long time, so eventually assembly language was made. Assembly language is easier for a human to read and can be written faster, but it is still much harder for a human to use than a high-level programming language which tries to mimic human language.
To program in machine code, the programmer needs to know what each instruction looks like in binary (or hexadecimal). Although it is easy for a computer to quickly figure out what machine code means, it is hard for a programmer. Each instruction can have several forms, all of which just look like a bunch of numbers to people. Any mistake that someone makes while writing machine code will only be noticed when the computer does the wrong thing. Figuring out the mistake is hard because most people cannot tell what machine code means by looking at it. An example of what machine code looks like:
05 2A 00
This hexadecimal machine code tells an x86 computer processor to add 42 to the accumulator. It is very difficult for a person to read and understand it even if that person knows machine code.
With assembly language, each instruction can be written as a short word, called a "mnemonic", followed by other things like numbers or other short words. The mnemonic is used so that the programmer does not have to remember the exact numbers in machine code needed to tell the computer to do something. Examples of mnemonics in assembly language include "add", which adds data, and "mov", which moves data from one place to another. Because 'mnemonic' is an uncommon word, the phrase "instruction type" or just "instruction" is sometimes used instead, often incorrectly. The words and numbers after the first word give more information about what to do. For instance, things following an "add" might be what two things to add together and the things following "mov" say what to move and where to put it.
For example, the machine code in the previous section (05 2A 00) can be written in assembly as:
Assembly language also allows programmers to write the actual data the program uses in easier ways. Most assembly languages have support for easily making numbers and text. In machine code, each different type of number like positive, negative or decimal, would have to be manually converted into binary and text would have to be defined one letter at a time, as numbers.
Assembly language provides what is called an abstraction of machine code. When using assembly, programmers do not need to know the details of what numbers mean to the computer, the assembler figures that out instead. Assembly language actually still lets the programmer use all the features of the processor that they could with machine code. In this sense, assembly language has a very good, rare trait: it has the same ability to express things as the thing it is abstracting (machine code) while being much easier to use. Because of this, machine code is almost never used as a programming language.
When programs are finished, they have already been transformed into machine code so that the processor can actually run them. Sometimes, however, if the program has a bug (mistake) in it, programmers will want to be able to tell what each part of the machine code is doing. Disassemblers are programs that help programmers do that by transforming the machine code of the program back into assembly language, which is much easier to understand. Disassemblers, which turn machine code into assembly language, do the opposite of assemblers, which turn assembly language into machine code.
An understanding of how computers are organized, how they seem to work at a very low level, is needed to understand how an assembly language program works. At the most simplistic level, computers have three main parts:
In most computers, memory is divided up into bytes. Each byte contains 8 bits. Each byte in memory also has an "address" which is a number that says where the byte is in memory. The first byte in memory has an address of 0, the next one has an address of 1, and so on. Dividing memory into bytes makes it "byte addressable" because each byte gets a unique address. Addresses of byte memories cannot be used to refer to a single bit of a byte. A byte is the smallest piece of memory that can be addressed.
Even though an address refers to a particular byte in memory, processors allow for using several bytes of memory in a row. The most common use of this feature is to use either 2 or 4 bytes in a row to represent a number, usually an integer. Single bytes are sometimes also used to represent integers, but because they are only 8 bits long, they can only hold 2 or 256 different possible values. Using 2 or 4 bytes in a row raises the number of different possible values to be 2, 65536 or 2, 4294967296, respectively.
When a program uses a byte or a number of bytes in a row to represent something like a letter, number, or anything else, those bytes are called an "object" because they are all part of the same thing. Even though objects are all stored in identical bytes of memory, they are treated as though they have a 'type', which says how the bytes should be understood: either as an integer or a character or some other type (like a non-integer value). Machine code can also be thought of as a type that is interpreted as instructions. The notion of a type is very, very important because it defines what things can and can’t be done to the object and how to interpret the bytes of the object. For instance, it is not valid to store a negative number in a positive number object and it is not valid to store a fraction in an integer.
An address that points to (is the address of) a multi-byte object is the address to the first byte of that object – the byte that has the lowest address. As an aside, one important thing to note is that you can’t tell what the type of an object is - or even its size - by its address. In fact, you can’t even tell what type an object is by looking at it. An assembly language program needs to keep track of which memory addresses hold which objects, and how big those objects are. A program that does so is "type safe" because it only does things to objects that are safe to do on their type. A program that doesn’t will probably not work properly. Note that most programs do not actually explicitly store what the type of an object is, they just access objects consistently - the same object is always treated as the same type.
The processor runs (executes) instructions, which are stored as machine code in main memory. As well as being able to access memory for storage, most processors have a few small, fast, fixed-size spaces for holding objects that are currently being worked with. These spaces are called "registers". Processors usually execute three types of instructions, although some instructions can be a combination of these types. Below are some examples of each type in x86 assembly language.
The following x86 assembly language instruction reads (loads) a 2-byte object from the byte at address 4096 (0x1000 in hexadecimal) into a 16-bit register called 'ax':
In this assembly language, square brackets around a number (or a register name) mean that the number should be used as an address to the data that should be used. The use of an address to point to data is called indirection. In this next example, without the square brackets, another register, bx, actually gets the value 20 loaded into it.
Because no indirection was used, the actual value itself was put into the register.
If the operands (the things that come after the mnemonic), appear in the reverse order, an instruction that loads something from memory instead writes it to memory: