Advanced programming in C
In C you program knowing where things are. The pointer is not an oddity of the language: it is the memory address, and understanding it is the difference between fighting the compiler and driving the hardware.
01Every variable has an address
A variable is a name for a place in memory. That place has an address, and C lets you work with it directly, something most languages hide.
| Operator | Reads as | What it gives |
|---|---|---|
&x | address of x | Where x is stored |
*p | contents of p | The value at the address that p holds |
int *p; | p is a pointer to int | Declares a variable that holds addresses |
It takes up space in memory, has its own address and can be assigned a value. The only special thing
is that this value is an address, and that the type says how big the pointed-to object is: that is what makes
p++ advance 4 bytes on an int* and 1 byte on a char*.
Walk through the program with the slider and watch what happens in memory on each line.
02Arrays, strings and pointers
In C the name of an array is the address of its first element. That is why these four expressions mean the same thing:
v[i] ≡ *(v + i) ≡ *(i + v) ≡ i[v]
The last one is a curiosity best left unused, but it shows how far the square brackets are merely a convenient notation for pointer arithmetic.
C does not check bounds. Writing to v[10] of a 5-element array compiles without
complaint and overwrites whatever is next to it: another variable, the function’s return address, anything
at all. That is the cause of half the security flaws in the history of software, and it is why
you must always pass the size along with the pointer.
A string is an array of char ended by a zero byte. That zero is not
decorative: it is the only thing that tells strlen or printf where the
text ends.
03Pass by value and pass by reference
C always passes arguments by value: the function receives a copy. If something in the caller has to be modified, you pass its address:
void double_it(int *x) { *x = *x * 2; }
…
double_it(&a); /* a is now twice as large */
Arrays seem to be an exception, but they are not: what gets passed is the address of the first
element, so the function can modify the contents. That is why void f(int v[]) and
void f(int *v) are exactly the same thing to the compiler.
You can also store the address of a function and call it through that address. This is how dispatch tables, callbacks and state machines are built, and it is the mechanism behind a microcontroller’s interrupt vectors.
04Dynamic memory
| Region | Who manages it | Lives until |
|---|---|---|
| Global and static variables | The compiler | The program ends |
| Stack (local variables) | Automatic | The function returns |
| Heap (malloc) | The programmer | Someone calls free |
int *v = malloc(n * sizeof(int));
if (v == NULL) { /* out of memory */ }
…
free(v);
v = NULL;
The three rules that prevent almost all problems: always check that malloc did not return NULL, free exactly once whatever was allocated, and set the pointer to NULL after freeing it so that a later use fails immediately instead of silently corrupting memory.
- Memory leak: allocating and not freeing. The program grows until the system kills it.
- Dangling pointer: using memory that has already been freed.
- Double free: calling free twice with the same pointer.
- Uninitialized pointer: using a pointer that never received a valid address.
In an embedded device that runs for months without restarting, a memory leak of a few bytes per hour ends in a failure after weeks. That is why many embedded projects flatly forbid malloc after startup.
05Modularizing
A serious program does not live in a single file. The criterion is to separate interface from implementation:
sensor.h: what can be used —prototypes, types and constants—, protected against multiple inclusion with#ifndef SENSOR_H.sensor.c: how it is built. Whatever is not declared in the header is markedstaticand stays private to the file.- Each
.cis compiled separately and the linker joins the object files.
That organization is what lets you replace one sensor module with another without touching the rest, and it is the basis of the libraries used in building software.
06In the lab
Write a program that prints, with %p, the addresses of a global variable, a
local one, one allocated with malloc and a string constant. Sort them and deduce the memory map of the
process.
Write them using only pointers, without square brackets. Compare with the standard library versions and measure times with long strings.
Compile a program with a malloc and no free and run it under valgrind. Interpret the
report, fix the program and check again that no memory is leaked.
07Common mistakes
- Confusing
*pwithp. One is the contents; the other, the address. - Returning the address of a local variable. Once the function returns, that memory is no longer valid.
- Forgetting the zero byte when building a string by hand.
- Allocating the wrong size:
malloc(n)instead ofmalloc(n * sizeof(int)). - Comparing strings with
==, which compares addresses and not contents: you have to usestrcmp. - Using
sizeofon a pointer expecting the size of the array: it gives the size of the pointer.
08Self-assessment
If int v[5] starts at 0x1000, what address is v + 2?
0x1008: pointer arithmetic advances in steps of sizeof(int), that is,
4 bytes per element.
What is the difference between char s[] = "data" and char *s = "data"?
The first is a modifiable 5-byte array, a copy of the text. The second is a pointer to a read-only constant: writing to it is undefined behavior.
Why can’t void f(int x) modify the caller’s variable?
Because it receives a copy. You have to pass the address: void f(int *x).
What happens if you call free twice with the same pointer?
Undefined behavior: in general it corrupts the allocator’s internal
structures. Setting the pointer to NULL after freeing it prevents this, because free(NULL) is
safe.
What is the value of sizeof(v) inside void f(int v[])?
The size of a pointer —4 or 8 bytes—, not that of the array. The size has to be passed as a separate parameter.
What is the point of declaring a function static in a .c file?
To make it private to that file and keep it out of linking: it avoids name clashes and makes clear what is interface and what is implementation.
09Further reading
- Brian W. Kernighan and Dennis M. Ritchie. The C Programming Language. 2nd ed. (Spanish edition, “El lenguaje de programación C”), Pearson, 1991. Chapter 5, on pointers and arrays, is still the best written explanation of the subject.
- Randal E. Bryant and David R. O'Hallaron. Computer Systems: A Programmer's Perspective. 3rd ed., Pearson, 2015. What really happens in memory when a C program runs.
- Peter van der Linden. Expert C Programming: Deep C Secrets. Prentice Hall, 1994. The traps of the language explained with humor and real cases.
- Herbert Schildt. C: The Complete Reference. 4th ed. (Spanish edition, “C: manual de referencia”), McGraw-Hill, 2001. A quick reference to the standard library, handy to keep next to the keyboard.