What C is for, and what it is not
A small language with no safety net, and why so much of the software you already use is written in it.
A processor understands a few hundred numbered operations and nothing else. Copy these eight bytes from here to there. Add these two numbers. Jump to that instruction if the last answer came out zero. It has no idea what a word is, or a price, or a list of customers.
Writing those numbered operations out by hand is possible, and for the first twenty years of computing it was the job. It is slow, it is unforgiving, and the result runs only on the one kind of processor you wrote it for. A programming language is a notation people can read and write, together with a program that turns that notation into those operations.
C was designed in 1972 to write an operating system, and that job shaped everything about it. It is a notation for the machine's own operations, one small step up. Most lines of C correspond to a handful of machine instructions that you could predict if you sat down and thought about it.
What that buys you
- Speed. Nothing is reading your program while it runs and nothing is tidying up behind you. The processor executes your instructions and only your instructions.
- Predictability. You decide where a value lives, how many bytes it takes and when it is thrown away. Two runs of the same program do the same work in the same order.
- Reach. Almost every processor ever manufactured has a C compiler: the controller in a washing machine, the phone in your pocket, a weather supercomputer.
- Access. An operating system describes what it can do for you in C. Anything the machine is capable of, C can ask for directly.
What it does not buy you
C is small. The list of things it does not do for you is longer than the list of things it does, and every item on it is work that lands on you.
- No text type. A piece of text in C is a run of characters in memory that you have to find room for and keep track of yourself.
- No collection that grows. There is no list you can keep appending to. You ask for a block of memory of a size you chose, and if you need more you ask again and copy.
- Nobody frees memory for you. Memory you asked for stays taken until you say otherwise. Forget, and the program swells until the machine gives up.
- No check when you read past the end of something. Asking for the eleventh item of a ten item block is not an error. It reads whichever bytes happen to be sitting there and carries on.
- No exceptions. A function that fails tells you by returning a particular value. If you do not look at it, you will not know it failed.
That reads like a warning and it is one. It is also the reason this course keeps showing you the machine. Once you can say what is in memory, how big it is and who is responsible for it, C stops being frightening and becomes the most direct tool there is.
Translated once, before it runs
Some languages are read and carried out a line at a time by another program while yours runs. C is not one of them. A compiler reads your entire file first and translates it into machine code, producing an executable: a file of instructions the processor can run with no help from anybody.
That has a consequence you should feel before you meet it by accident. A mistake the compiler can see stops the whole build. A 400 line program with one missing semicolon on line 380 does not run the first 379 lines and then stop. Nothing runs at all. You get a complaint instead of a program.
| C | Carried out as it goes | |
|---|---|---|
| Mistakes in the code itself are found | before anything runs | when that line is reached |
| What you run | a file of machine code | your source, plus the program reading it |
| Reading past the end of a block | reads whatever is there | stops with an error |
| Memory you finished with | you release it | released for you |
| Cost of one step of your program | the machine instructions you asked for | those, plus working out what the step meant |
That last row is where a difference in speed comes from. Before a language that is carried out as it goes can add two numbers, it has to look at the line again, work out that the operation is addition, find both values and check what kind of things they are, and only then add. C settles all of that once, before the program starts, so the same loop can finish ten to a hundred times sooner. Nothing about C is magic; there is simply less happening.
Everything in that first column is on show in the box below. The text is sent to a C compiler, the compiler produces a program, and the program is run. Press play and you will see what it printed.
One name in it needs a word now, and the next lesson gives the detail. printf is the standard library's function for writing text out. It prints the characters between the quotation marks and nothing more, so the \n on the end is how you ask for the output to move to a new line afterwards.
Change the words between the quotation marks and run it again. Then delete the \n at the end and look closely at where the output stops.
Now something less friendly. C's arithmetic is the machine's arithmetic, and a machine divides whole numbers the way you did before anybody mentioned fractions: it gives you the whole part and throws the remainder away.
Two more marks appear in the next box. A %d inside the quotation marks is a gap where a whole number is dropped in, and %f is the same gap for a number with a decimal part. The value that fills the gap is written after the closing quotation mark. Again, the detail is the next lesson's job; for now read %d as here goes a number.
Change every 7 to a 9 and run it again. Then change the last line's 2.0 to 0.0: dividing a decimal by zero gives inf, which is C's stand in for a result too big to write down. Do not read anything into that about the first line. Dividing a whole number by zero is a different matter entirely, undefined behaviour that stops the program on some machines and quietly hands back a meaningless number on others.
Three answers, from three different questions. 7 / 2 divides one whole number by another and gives 3, the whole part. % gives what was left over, 1. 7.0 / 2.0 is a division of numbers that are allowed a fractional part, so the answer keeps its half. The slash means two different operations depending on what you handed it, and no warning is given when you choose the wrong one.
A program splits a bill of 7 pounds between 2 people with 7 / 2 and prints 3. Where did the missing 50p go?
Nowhere. It was never calculated. Both 7 and 2 are whole numbers, so C used whole number division, which produces a whole number and discards the rest. There is no rounding and no error: the remainder simply does not appear in the answer. Money is usually held in pence for exactly this reason, so that the arithmetic stays in whole numbers and nothing can be quietly lost.