As all programs are run via a computer, it is essential to have a basic idea of how computers run in order to program one. Programs that work closely with hardware, such as drivers (programs that control hardware such as printers of graphics cards) or to a lesser extent audio and video processing software, require a detailed knowledge of how this or that hardware runs.
For most programs, however, a generalist’s view is sufficient. This chapter examines in suitably generalist terms what a computer is, how it is architected, and how code is related to the architecture at the machine level.
Math
Is programming a form of math? Take the following algebraic equation
x + y = z
and compare it to this line of code
z = x + y
As these look more or less identical, they support the view that computing (‘hey, the clue is in the name’) is math. This view is however a misconception and the algebra and code are fundamentally different. The algebra represents numbers but the code is an instruction to the computer.
When a mathematician says ‘x + y = z’, they mean that the abstract values ‘x’ and ‘y’, when added, are equal to the abstract value ‘z’. For example, 1 + 2 = 3, 4 + 9 = 13, 1745 + 2123 = 3868 and so forth.
However, code ‘z = x + y’ is not an algebraic equation. ‘x’, ‘y’ and ‘z’ here are pointers to data stored in the computer’s memory. ‘x’, for example, might have a stored value of ‘Hello,’ and ‘y’ of ‘ world!’. So the value assigned to ‘z’ would be ‘Hello, world!’.
'Hello, + world! = z
This is not an algebraic equation!
Even integral numbers would not make it an algebraic equation, for ‘z’ = ‘1’ + ‘2’ simply means ‘put the sum of 1 and 2 into the memory pointed to by z’. This is formally the same as if the values were ‘Hello,’ and ‘ world!’. An algebraic ‘x’ is an abstraction of a number; a code ‘x’ is a reference to a value in the computer’s memory.
However, to pick apart what a computer’s memory actually is, we need do understand what a computer is. We will begin with a brief overview of the history of computing machines.
It is interesting that the once-universal term ‘computer’ given to computing machines is in danger of vanishing. Tablets and phones are clearly computers but never, or hardly ever, called by that name. Nowadays computers are an essential part of cars and televisions, but cars and televisions are not computers. As more and more aspects of life and technology ‘go digital’ it is probably the case that the computer itself is becoming a historical device, like the video player or analog TV. The computer, perhaps we can begin to see, was only a computer in its heyday, when it was the only computing machine around (pace the pocket calculator).
Computing Machines
What is a computer? Before computers, the answer would invariably be ‘someone who computes’. There were no machines that could compute (apart from outlier contraptions such as the genial Babbage ‘engines’). There were devices such as the abacus that aided computation, but an abacus is not in itself a computer.
Perhaps the nearest commonplace thing to a machine-computer was the clock, which effectively computed the time. The most sophisticated clocks (and watches) were marvels of mechanical engineering.
It is in the late 1940’s that electronic machines able to compute were first built. The groundwork for these machines was laid over a long reach of history via the work of many mathematicians, logicians, linguists and engineers, but the reality of ‘what a computer is’ begins only with the building of a fully-functional one. Functional electronic computers first appeared in the mid-late 1940’s.
The new machine-computers — soon simply ‘computers’, as they swiftly made the notion of mere human computers outmoded — were at first gigantic devices that performed very specific and limited tasks. They were, however limited, proper commercial products bought by big businesses. The early computers did what was asked of them well enough.
The development of the computer in those days was characterised by evolution coupled to many dead-ends. Each new computer required a new design and solved a new problem. Some of these designs and solutions stuck, many did not. Gradually, however, certain basic principles emerged. The computer came to be a processor of code stamped onto a punched card. At this groundbreaking stage, there was now code and there was machine and the resulting computations could be stored externally onto tape.
In other words, there was the computer and there was computing and the computer had now reached the point of being a generalist computing machine.
code > [ computer] > data
Many well-known components of the modern computer, however, emerged gradually, including the most basic functional unit of any computer: the byte. Things in the ‘real world’ are collections of electrons and quarks. A computer is a collection of bits. Just as quarks are collected into protons, bits are collected into bytes. How many bits in a byte? It took a while before the canonical figure of eight was generally adopted.
The Personal Computer
It was the PC that gave us the ‘classical’ shape and form of the computer, which takes us up to the early-mid 1980’s.
A generalist can understand a classical PC as working somewhat as follows. The PC’s activity began with the BIOS, a chip that ran when the machine started. The BIOS checked the hardware and then fired up the operating system (popularly MS-DOS in the early days). A classical PC had at its core a hard disk, memory (RAM), a graphics card and a processor. It was attached to more or less essential ‘peripherals’ such as a monitor, keyboard and of course the floppy disk drive (in all its creaking glory).
The classical PC stored files on its HDD. Files contained data or code (‘programs’). Programs themselves could be written in a code editor, then saved to disk and run (or ‘executed’). Of course, for most users programs were bought off the shelf — word processors and spreadsheets and games and the like.
A classical PC, then, was a collection of codes stored on a HDD ready to be converted into running programs by being executed as a process by the operating system (for example, MS-DOS).
WRITER.EXE > [ MS-DOS ] > [PROCID_14E77]
Architecture
The fundamental structure of a computer, from the first machines to the classical PC, is called its architecture. A programmer ought to possess a generalist’s understanding of computer architecture.
As we have seen, the architecture of computers evolved between the 1940’s and the 1980’s. By the latter period, the fundamental architecture of the modern computer was a done deal. Even today’s tablets and phones are simply reimagining’s of the first classical PC’s (so we have monitor > touchscreen, keyboard > on-screen, mouse > touch, HDD > SSD, graphics card > graphics chip).
If the atomic core of a computer is the bit, the active core is the processor and the world of the processor is the world of the byte (eight bits), a world which is illustrated below :
10110101
11100111
00110001
10000011
Fascinating, eh? Well, fascinating or not, that is bytes and that is computing. But why bytes? To answer that is not to answer why computers exist at all, but why they exist in the form they do.
Counting
Mathematics is in the end a sophisticated form of counting. Counting, in turn, is a counting of things. Things is what pre-scientific people counted. They counted 5 or 67 or 123 sheep. No one was interested in zero sheep.
The default European method of counting is based on the ten fingers of the human body. It therefore has ten names for the numbers. All bigger numbers are based on these ten names, though this is often hidden by the changes in the sound of language over time.
While ‘hundred’ for example is unlike any other English number’s name, it was once pronounced something like ‘dkmtom’ and so clearly based on ‘dekmt’ (as ‘ten’ was once pronounced).
With the adoption of writing, numbers as well as words were set down on the page (or tree bark or stone). An example to consider here is the ingenious Roman system, which worked via three numeric groupings: 10, 100 and 1,000. Within each grouping there was a beginning, a midpoint and an endpoint, each written with a single symbol. The numbers in between were written using an additive or subtractive principle. So, V is the midpoint between 1 and 10, and 4 is ‘5 – 1’ (IV) and 6 is ‘5 + 1’ (VI). Similarly, 9 is ’10 -1 (IX). The additive principle is seen in the sequences I, II, III and VI, VII and VIII.
The three groupings are shown below :
I .. V .. X 1-10
X .. L .. C 10-100
C .. D .. M 100-1,000
With a mere six symbols, all the numbers from 1 to 1,000 can be easily written down. So just as we have VII (7), there is LXX (70) and DCC (700), or IV (4) and XL (40) and CD (400). It is very noticeable how this writing system is tied to the decimal system preserved in the names of Roman numbers.
The significant things to notice here is how this Roman writing counts only things, and how much this makes sense in pragmatic terms. We might even ask, Why would anyone want to count anything but things?
Decimal
The notion of a zero symbol is a profoundly unobvious one. It is only because it is so familiar to us that we find it difficult to conceive of a counting system without it. However, if we compare it to the Roman system, things do not look good at a first glance, for set against the mere six Roman symbols (IVXLCDM) that can be used for every number up to 1,000, the decimal system requires nine counting symbols (123456789) plus the zero symbol (0) to get us to 10. Oh dear.
Things begin to get better though. When we have used up our counting symbols as we reach the number ‘ten’, we can now bring our zero symbol into play. We shift our first counting symbol to the left of the zero symbol, which gives us ’10’. The ‘1’, we now say, means ‘one lot of ten’, to which we add the zero. 10 + 0 of course remains 10. In that lies the zero’s profound little trick.
We can now use the same principle for all the rest of the counting symbols. ’15’ is simply ‘one lot of ten plus 5′. Note here that there is no longer any conceptual shift between additive (VI) and subtractive (IV) symbols. We can also scale up our counting symbols on the left and say, for example, that ’20’ means ‘two lots of ten plus zero’. Counting from 10 to 100 is the same as counting from 1 to 10 except for the extra zero on the end.
When we max out our symbols at ’99’, we simply re-use our basic principle and shift the ‘1’ to the left again so that we have two zeros to the right. The ‘1’ now means ‘one hundred’ which of course remains as-is when we add our two zeros to it. From this point, we can go on shifting the ‘1’ indefinitely: 1000, 10000, 100000000000000, etc.
With our zero-based system in place, we can write numbers that would be absurdly complex to express using the Roman system, and all because we introduced a symbol that is an anathema to counting — zero!
Binary
When the first computers were being designed, counting was a decimal thing. So how would you store numbers on the computing machine you are trying to build? Clearly, that is fundamentally asking, How do I store digits on the machine? At first glance that means decimal digits.
Early computers used valves and an obvious way to indicate a numeric value was by using voltages. For a decimal digit, you would measure any of ten voltages and each voltage would represent a digit. If you had two valves, you could fetch back a two digit number.
This is where the genius of the zero-based writing system comes in, for it is eminently capable of further abstraction in the way the Roman system is not. The underlying principle of the zero-system is that it uses x counting symbols plus another for zero and it shifts the first symbol to the left when the symbols have been used up.
If we use seven counting symbols, ’10’ means ‘one lot of eight plus zero’. If there are more than nine counting symbols, we can simply re-use the letters of the alphabet for numbers greater than 9. With fifteen counting symbols, we end up with: 123456789ABCDEF. Here, ‘F’ means fifteen and ’10’ means ‘one lot of sixteen plus zero’. The minimum number of counting symbols you can have is one, where ’10’ means ‘one lot of two plus zero’ and so here, meet binary.
Binary is terrible for humans, but for designing computers it is booyakasha because all we need now is two voltages to make a digit, one for the solitary counting symbol and one for the zero symbol. Let’s say zero volts equals ‘0’ and 1 volt equals ‘1’. So ’16’ is 10000000 in binary and that would be eight valves, the leftmost being fired up with 1 volt, the rest set to zero volts.
The binary counting system is a perfect fit for the fundamental atom of the computer, the bit. It is binary that makes it possible to build a working computer. Binary and decimal are arithmetically identical in the zero-based writing system. Better still, so are octal (seven counting digits) and hexadecimal (fifteen counting digits). What is a standard byte? Eight bits. Octal. Two bytes, sixteen bits: hexadecimal.
Binary, octal, decimal, hexadecimal: a programmer needs to have a basic familiarity with all of these.
Note that a counting system above hexadecimal would not be practical. The next step up from hexadecimal (base 32) would have thirty-one counting numbers: 123456789ABCDEFGHIJKLMNOPQRSTUV. Would you be able to work out what the number BOAW2T is in decimal? Certainly not BOOJAKASHA!
Code
What is 1 + 1? How could you make a computer be able to answer this question? The answer is, if you think on the question, not at all obvious. A key to understanding the answer is to realise that there are four actors in the sum: the two addends, the sum, and the ‘+‘ symbol. Understand this and you start to see how a computer ‘thinks’.
Below is a very simple computer-like way of saying one plus one :
01. LOAD 1
02. ADD 1
03. STORE
The key to the answer is that there needs to be somewhere for data to both come from and go to. Even for something as simple as 1 + 1, the 1’s have to exist somewhere. In this very simple sequence of instructions, the first loads a ‘1’ value (the data). Into what and from where we do not care for the moment. The point is that we have made it the computer’s focus of attention. This done, we can run our add command. This will add 1 to the data we have just loaded. The data now has a value of ‘2’. The last thing to do is store our data. Again, where we do not care for now.
We can use this super-simple system to observe how our initial ‘algebra’ sum (z = x + y) might work and so see how un-algebra-like it really is. Here is the sum :
01. SET x 1
02. SET y 1
03. LOAD x
04. ADD y
05. STORE z
Note that we could just as well have written (as per our early example) :
01. SET x "Hello,"
02. SET y " world!"
The core of the processor is not, then, monolithic, for we see commands (SET, LOAD, ADD, and STORE) along with data. These are handled by the processor using registers. Registers run commands and store data.
When we say ‘SET x 1’, that is where ‘x’ will go — to a data register — and the register will now be storing a value of ‘1’.
Our first and simplest example (LOAD 1, ADD 1, STORE) has three registers (A, B, C), one that executes commands, one that stores the data that commands act on and a third that stores data permanently (until it is overwritten). So this is what our command sequence is saying :
- load the value 1 onto register B
- add the value of 1 to the contents of register B
- store the contents of register B onto register C
Language
Computers do not in any way shape or form directly understand English. In computerese, our simple sequence of commands might look something like this :
00000001 00000001
00000002 00000001
00000003
This is machine code. It is this and only this that a computer understands. In the above example, we observe commands and data as the machine sees them. Look at the first line. The first and second parts seem identical, but course they are not. Each command has a numeric look-up value. Here, ‘00000001’ stands for the ‘LOAD’ command. The second part of the statement is data and it has a value of 1. Note that, when the computer receives the LOAD command, it knows what to do with the data value: it will set registry B to ‘1’.
As humans do not do reading machine code at all well, a programmer would work with the englished equivalent of the sequence, which we have met with earlier :
01. LOAD 1
02. ADD 1
03. STORE
This represents what is called assembly language. Assembly languages map very closely to machine code. Without the binary, assembly language is easier to read, but not easy to read. Because it must follow the machine so closely, it is very precise and very verbose.
Enter the programming language. C, C++, C#, Java, Kotlin, Haskell, Prolog, LISP, Lua, Ruby, Python, JavaScript, COBOL, BASIC, Fortran, Pascal, Ada, Go, Rust, Brainfuck — you could almost utter a syllable and that there is or was the name of a programming language.
Programming languages — even C — are human-readable. They work at a much higher level than machine code or assembly language. It is important to understand, though, that they are the same thing. That is to say, a program written in any programming language cannot be understood by the computer. Your program code must be converted into machine code (perhaps via assembled code). That done, the high-level language and the machine code are one and the same.
In many languages, this conversion process is called compiling. Compilation is performed by — a compiler. Other languages are interpreted. A compiler takes your entire program and turns it into machine code; an interpreter does the same thing line by line as the program runs. C is an example of a compiled language and BASIC of an interpreted one.
It is programming languages that a programmer learns, just as a French interpreter must learn French. A programming language is in a sense interpreting the computer. To use a programming language, then, does not require a detailed knowledge of the underlying machine, but a general awareness of it is I would say more or less essential. It is with this awareness that a programmer can distinguish between algebra and code: between x + y = z and z = x + y.
Conclusion
Computing is not algebra. It is not math. It can do algebra and all sorts of math. But that is not what it is. At a fundamental level, it is commands and data. In the simple sum of 1 + 1, the ‘+’ is a command and the ones are data. The computer is not (not to its knowledge) doing a sum. As far as it can tell it is performing a command on data. The data, moreover, must always be stored somewhere. As the machine processes any sequence of command, data must be both fetched and sent to and from registers.
That is a computer program, a relentless sequence of fetching and sending and commanding, this sequence having been read from that alien thing — alien to the computer — we call ‘code’.