Catto / Topic Map · Computer Science II Level 2
Computer Science II · 120 h · Topic 1 of 8

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.

Pointers Addresses Arrays malloc Modularization

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.

OperatorReads asWhat it gives
&xaddress of xWhere x is stored
*pcontents of pThe value at the address that p holds
int *p;p is a pointer to intDeclares a variable that holds addresses
A pointer is just another variable

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*.

Lab · memory, step by step

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.

The array does not know its own size

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.

Function pointers

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

RegionWho manages itLives until
Global and static variablesThe compilerThe program ends
Stack (local variables)AutomaticThe function returns
Heap (malloc)The programmerSomeone 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.

The four classic errors
  • 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 marked static and stays private to the file.
  • Each .c is 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

Exercise 1 · Seeing the addresses for real

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.

Exercise 2 · Implementing strlen and strcpy

Write them using only pointers, without square brackets. Compare with the standard library versions and measure times with long strings.

Exercise 3 · Hunting a memory leak

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 *p with p. 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 of malloc(n * sizeof(int)).
  • Comparing strings with ==, which compares addresses and not contents: you have to use strcmp.
  • Using sizeof on 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.
Development of the topic “Advanced programming in C” of Computer Science II (Level 2), based on the curriculum of the UTN Electronic Engineering program, 2023 curriculum — Ordinance No. 1849 of the UTN Higher Council. Back to the Topic Map · catto.ar