Version without JavaScript, read-only: workshops, quizzes and running code are not available here. Full version · Catalogue · Sign in
Lecture notes · chapter 0
Introduction
Why C, what “compiled” and “low-level” mean, and the few writing rules we will keep to all semester.
Why C
You learnt to program in Caml: a language in which you describe what you compute, and the machine takes care of the rest. Memory is allocated by itself, freed by itself, and a list is a value that you do not modify.
C is at the other end. It was written in the early 1970s, by Dennis Ritchie at Bell Labs, to rewrite the Unix system, which until then was written in assembly. So it had to allow everything assembly allows (reading and writing any byte of memory, talking to the hardware) while remaining readable and portable from one machine to another. Fifty years later, it is still the language of operating system kernels (Linux, the BSDs, the core of Windows and macOS), of base libraries, of embedded systems, and of the interpreters of other languages: the Python interpreter is written in C, and so is the OCaml runtime.
Learning C is therefore mostly learning what the machine does when a program runs: where the data is stored, what a function call costs, how a program reads a file or starts another one. What Caml hid from you, C shows you, and entrusts to you.
Families of languages
Compiled or interpreted
These definitions are theoretical. In practice, almost every language has interpreters and compilers: OCaml has an interactive interpreter (ocaml), a compiler to native code (ocamlopt) and a third one to bytecode (ocamlc). Bytecode is an intermediate language, which is that of no real microprocessor: it is then interpreted, or compiled on the fly during execution (just-in-time). That is also what Java and C# do.
C, for its part, is almost always compiled. This is a property of its tools rather than of the language, but it has shaped the language: everything that can be decided before execution is, and nothing is checked during it.
Low-level or high-level
| Distance from the hardware | close | strong abstraction |
|---|---|---|
| Memory management | by hand | automatic |
| Safety | weak | strong |
| Writing speed | slow | fast |
| Execution speed | fast | slower |
| Examples | assembly, C, C++ | Java, Python, OCaml, JavaScript |
There is no sharp boundary: it is an axis, and languages sit more or less far along it. C is very close to assembly. What you gain is control: you know where each byte is, and what each line costs. What you lose is safety: reading the eleventh cell of a ten-cell array causes no error, the program simply reads whatever happens to be there in memory.
Writing code that can be read again
A program is read far more often than it is written: by you in three weeks, by your teammates, by whoever marks it. A few rules, which we will keep to throughout the course.
Style
- Indent the code, always in the same way. The compiler does not care; the reader does.
- Stay consistent: once a convention is chosen (braces, naming, spaces), stick to it throughout the project.
- Stay clear: one line does one thing. C lets you write very dense expressions; that is no reason to do so.
- Explicit and short identifiers:
nb_lignesrather thannornombre_de_lignes_du_fichier_courant. One-letter names are reserved for loop indices. - 80 columns are enough. A longer line gets split, or hides a function that is waiting to be born.
Comments
- Even good code needs comments: the code says what it does, the comment says why, and what the code cannot say (what the function expects of its arguments, who must free the memory it returns).
- A wrong comment is worse than no comment: update the comments along with the code.
- Time spent writing a good comment is never wasted.
Optimisation
C has a reputation for being the language of speed, and it is tempting to write “fast” code from the very first line. Two quotations to keep in mind:
Make it right before you make it fast. Make it clear before you make it faster. Keep it right when you make it faster. — P. J. Plauger, The Elements of Programming Style
We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. — Donald E. Knuth, Structured Programming with go to Statements (1974)
Correct first, then clear; fast afterwards, if necessary, and by measuring. The compiler, with -O2, already does better than most tricks written by hand.
The course outline
- Compiling and running: how a
.cfile becomes a program, header files,make. - Pointers and arrays: addresses, passing by address, pointer arithmetic.
- A program's memory: static area, stack and heap;
mallocandfree; virtual memory. - Data structures: lists, queues, trees and vectors, and how to organise their code.
- Input and output: file descriptors,
readandwrite, redirections and pipes. - Processes:
fork,waitandexec, to launch programs the way a shell does. - Parallelism: several threads of execution sharing memory, and how to stop them from treading on each other's toes.