§ 01 — Computer Programming 2
Think of memory as a long row of numbered boxes, one byte each. Every variable you declare is given some of those boxes, and the number of its first box is its address. An int usually takes 4 boxes, a double 8, a char 1.
The address-of operator & gives you a variable's address. You have used it since CP1 without thinking about it: scanf("%d", &num) hands scanf the address of num so it knows where to store what you type.
#include <stdio.h>
int main(void) {
int units = 21;
double gwa = 1.75;
printf("units = %d, stored at %p\n", units, (void *)&units);
printf("gwa = %.2f, stored at %p\n", gwa, (void *)&gwa);
printf("an int uses %d bytes, a double uses %d bytes\n",
(int)sizeof units, (int)sizeof gwa);
return 0;
}
Sample output (your addresses will be different):
units = 21, stored at 0x7ffd5e8a3b4c
gwa = 1.75, stored at 0x7ffd5e8a3b40
an int uses 4 bytes, a double uses 8 bytes
%p is the conversion for addresses. C expects a void * there, hence the (void *) cast.a[1] is always sizeof a[0] bytes after a[0].sizeof in code instead of writing 4.& can be used on& needs something that has a place in memory:
&units&scores[2]It cannot be used on a constant or the result of a calculation. &5 and &(x + 1) are compile errors, because those values are not stored in a box of their own.
So far, a function could only receive copies of your variables (call by value). An address lets a function reach the original variable and change it. That is what scanf does, and it is what the rest of this lesson builds on: you will store addresses in variables (pointers), follow them with *, and move them through arrays.
A pointer is a variable that holds an address. You declare one by putting * before its name:
int *p; /* p can hold the address of an int */
double *q; /* q can hold the address of a double */
char *c; /* c can hold the address of a char */
Read int *p as "p is a pointer to int". The type matters: an int * should only point at ints, because the type tells C how many bytes to read when it follows the pointer.
#include <stdio.h>
int main(void) {
int x = 10;
int *p = &x; /* p holds the address of x */
printf("x = %d\n", x);
printf("*p = %d\n", *p); /* read x through p */
*p = 25; /* write x through p */
printf("x = %d\n", x);
printf("p == &x is %d\n", p == &x);
return 0;
}
Output:
x = 10
*p = 10
x = 25
p == &x is 1
p = &x makes p point to x. Now p holds x's address.*p follows the pointer to the variable it points to. This is called dereferencing. *p reads x, and *p = 25 changes x, even though the statement never mentions x.p itself never changed. It still holds the same address, as the last line shows.The same program as boxes and an arrow (the addresses are examples):
*The same symbol does two different jobs, and mixing them up is the most common source of confusion:
| Where it appears | Meaning | Example |
|---|---|---|
| In a declaration | "this variable is a pointer" | int *p; |
| In an expression | "the value at this address" | *p = 25; |
So int *p = &x; means "declare a pointer p and store &x in it". It does not store anything in *p.
NULL: pointing nowhereA pointer that does not point to anything yet should hold NULL (defined in <stdio.h> and other standard headers):
int *p = NULL;
if (p != NULL)
printf("%d\n", *p); /* only follow p when it points somewhere */
Following a NULL pointer crashes the program on most systems. A pointer that was never initialized is worse: it holds a leftover garbage address, and writing through it can silently damage other data. Give every pointer a value when you declare it, either a real address or NULL.
int *p, q; /* p is a pointer, q is a plain int */
int *r, *s; /* both are pointers */
The * belongs to the name next to it, not to the type. Writing it as int* p, q; does not change this, and only makes it easier to misread.
In CP1 Lesson 6 you met call by reference: pass &x, receive it as a pointer, and change the caller's variable with *. Now that you know what a pointer is, here is exactly why it works, and the two jobs it is used for.
#include <stdio.h>
void swap_copy(int a, int b) { /* gets copies of x and y */
int temp = a;
a = b;
b = temp; /* only the copies are swapped */
}
int main(void) {
int x = 3, y = 8;
swap_copy(x, y);
printf("x = %d, y = %d\n", x, y);
return 0;
}
Output:
x = 3, y = 8
a and b are new variables that start as copies. Swapping them does nothing to x and y, and the copies disappear when the function returns.
Pass the addresses, and swap the values they point to:
#include <stdio.h>
void swap(int *a, int *b) {
int temp = *a; /* value at a's address (x) */
*a = *b; /* x gets y's value */
*b = temp; /* y gets x's old value */
}
int main(void) {
int x = 3, y = 8;
swap(&x, &y);
printf("x = %d, y = %d\n", x, y);
return 0;
}
Output:
x = 8, y = 3
Inside swap, a holds the address of x, so *a is x. Every change through *a and *b lands directly on main's variables.
return gives back only one value. When a function must produce two or more results, the caller passes the addresses where they should go:
#include <stdio.h>
void min_max(const int arr[], int n, int *min, int *max) {
int i;
*min = arr[0];
*max = arr[0];
for (i = 1; i < n; i++) {
if (arr[i] < *min) *min = arr[i];
if (arr[i] > *max) *max = arr[i];
}
}
int main(void) {
int temps[5] = {31, 28, 34, 30, 27};
int lo, hi;
min_max(temps, 5, &lo, &hi);
printf("lowest %d, highest %d\n", lo, hi);
return 0;
}
Output:
lowest 27, highest 34
lo and hi have no values before the call. The function fills them through the pointers. This is exactly what scanf("%d %d", &a, &b) does: it "returns" two numbers by writing them to the addresses you gave it.
void swap(int *a, int *b).&: swap(&x, &y). (An array name is already an address, so min_max(temps, ...) needs no &.)*a for the value. Writing a = b; would only change which address the local pointer holds, and the caller would see nothing.When a pointer points into an array, you can move it with + and -. C does the size calculation for you: adding 1 to a pointer moves it to the next element, not the next byte. For an int *, p + 1 is sizeof(int) bytes (usually 4) further along. For a double *, it is 8 bytes.
#include <stdio.h>
int main(void) {
int a[5] = {10, 20, 30, 40, 50};
int *p = &a[0];
int *q = &a[3];
printf("%d\n", *p); /* a[0] */
printf("%d\n", *(p + 2)); /* two elements later: a[2] */
p++; /* p now points at a[1] */
printf("%d\n", *p);
printf("%d\n", (int)(q - p)); /* elements between them */
printf("%d\n", *q - *p); /* difference of the values */
printf("%d\n", p < q); /* p comes before q */
return 0;
}
Output:
10
30
20
2
20
1
| Operation | Meaning | Example (p at a[1], q at a[3]) |
|---|---|---|
p + n, p - n | the address n elements later or earlier | p + 2 points at a[3] |
p++, p-- | move p itself one element | p++ moves it to a[2] |
q - p | how many elements apart (both in the same array) | q - p is 2 |
p < q, p == q | which comes first, or the same place | p < q is 1 |
Adding two pointers, or multiplying them, has no meaning and does not compile.
Pointer arithmetic is only defined inside one array, plus the single position just past its last element (which you may compare against but never dereference). p + 10 on a 5-element array is undefined behavior even if you never read it. The array bounds rules from Lesson 1 apply to pointers too.
* and ++ are easy to misread. Compare these, all starting from p at a[0]:
#include <stdio.h>
int main(void) {
int a[3] = {5, 10, 15};
int *p = a;
printf("%d\n", *p + 1); /* value at p, plus one: 6 */
printf("%d\n", *(p + 1)); /* the next element: 10 */
printf("%d\n", *p++); /* use *p, then move p: 5 */
printf("%d\n", *p); /* p moved to a[1]: 10 */
(*p)++; /* add 1 to the value at p */
printf("%d\n", a[1]); /* 11 */
return 0;
}
Output:
6
10
5
10
11
*p + 1 adds to the value. *(p + 1) moves the pointer.*p++ reads the value first, then moves the pointer (the ++ applies to p, not to *p).(*p)++ increases the value and leaves the pointer where it is.When in doubt, add parentheses. Nobody loses points for *(p + 1).
Pointers and arrays are closely linked in C. Once you see how, a lot of earlier rules suddenly make sense: why scanf("%s", name) needs no &, and why a function can change the array you pass it.
In almost every expression, an array name turns into a pointer to its first element. So for int a[5];:
a is the same address as &a[0].a + i is the address of a[i].a[i] is exactly *(a + i). The bracket notation is just a convenient way to write pointer arithmetic.#include <stdio.h>
int sum(const int *a, int n) {
const int *p;
int total = 0;
for (p = a; p < a + n; p++) /* walk from first to last */
total += *p;
return total;
}
int length(const char *s) {
const char *start = s;
while (*s != '\0')
s++;
return (int)(s - start); /* how far s moved */
}
int main(void) {
int nums[4] = {3, 6, 9, 12};
char course[] = "BSIT";
printf("%d %d\n", nums[2], *(nums + 2));
printf("sum = %d\n", sum(nums, 4));
printf("length = %d\n", length(course));
printf("sizeof nums = %d\n", (int)sizeof nums);
return 0;
}
Output (on a system with 4-byte ints, which is almost every one today):
9 9
sum = 30
length = 4
sizeof nums = 16
In a parameter list, int a[] and int *a mean exactly the same thing. That is why:
sum(nums, 4) passes only an address, and the function cannot tell how long the array is. You pass n.sizeof inside the function gives the size of a pointer, while sizeof nums in main gives the whole array, 16 bytes.Inside the function you may use either notation. a[i] and *(a + i) compile to the same thing, so use whichever is clearer.
A pointer is a variable; an array name is not. So:
int nums[4] = {3, 6, 9, 12};
int *p = nums; /* fine: p now points at nums[0] */
p++; /* fine: p moves to nums[1] */
But nums++ and nums = p do not compile. The array always stays where it was created.
char * versus char [] for stringschar s[] = "BSIT"; /* an array: your own 5-byte copy of the text */
const char *t = "BSIT"; /* a pointer to the literal, which is read-only */
You may change s[0], but not t[0]: changing a string literal is undefined behavior and usually crashes. Marking t as const char * makes the compiler catch the mistake for you. The length function above takes const char *s, so it accepts both kinds.
Pointer bugs are hard to find because many of them compile, and some only fail sometimes. Learn to spot these on sight.
int *p;
*p = 5; /* WRONG: p holds a garbage address */
int x;
int *q = &x;
*q = 5; /* right: q points at x first */
An uninitialized pointer holds whatever bits were left in memory. Writing through it may crash, or may overwrite some other variable and cause a strange bug much later. Compilers can warn about it (-Wall turns this on), which is one more reason to compile with warnings and read them.
NULL pointerNULL means "points nowhere", so *p on a NULL pointer crashes on most systems. When a function may return NULL, such as strchr when the character is not found, check before you use the result:
#include <stdio.h>
#include <string.h>
int main(void) {
char email[] = "juan@school.edu.ph";
char *at = strchr(email, '@');
if (at != NULL)
printf("domain: %s\n", at + 1);
else
printf("no @ found\n");
return 0;
}
Output:
domain: school.edu.ph
strchr returns a pointer to the @ inside email, so at + 1 is a pointer to the rest of the string, and %s prints from there to the '\0'.
p and *pint x = 7;
int *p = &x;
if (*p == 7) /* compares the VALUE: right */
if (p == 7) /* compares the ADDRESS with 7: wrong */
p = 9; /* tries to store 9 as an address: wrong */
*p = 9; /* stores 9 in x: right */
When the left side is a pointer and the right side is a plain number (or the other way around), something is missing: a * or a &. Compilers warn about these, and newer ones refuse to compile p = 9;.
int *p, q; /* q is an int, not a pointer */
Writing q = &x; then mixes an address and an int, which is mistake 3 again. Declare each pointer with its own star: int *p, *q;.
& in a callint n;
scanf("%d", n); /* WRONG: passes n's garbage value as an address */
scanf("%d", &n); /* right */
scanf writes to whatever address you give it. Passing the value of n makes it write to a random place. Compilers warn: "format specifies type int * but the argument has type int".
int *bad(void) {
int result = 42;
return &result; /* WRONG: result disappears when bad() returns */
}
Local variables live only while their function runs. The pointer you get back points at memory that is about to be reused, so reading it gives garbage or crashes. Return the value itself (int good(void) { return 42; }), or let the caller pass a pointer to its own variable, as min_max did.
*(a + 5) on a 5-element array is the same bug as a[5]: outside the array, undefined behavior. When you walk an array with a pointer, the loop test is p < a + n, never p <= a + n.
&x gives the address of x. A pointer is a variable that holds an address: int *p = &x; makes p point to x, and %p with a (void *) cast prints it.* means "is a pointer"; in an expression *p means "the value at that address". *p = 25; changes x without naming it. Initialize every pointer to a real address or NULL, and never follow a NULL or uninitialized pointer.swap(&x, &y)) and work through * inside the function.q - p counts the elements between them, and pointer arithmetic is only valid inside one array.a[i] is *(a + i) and an array parameter is really a pointer. But an array name cannot be moved or reassigned, and sizeof only sees the whole array where it is declared.Change where p points (try &a[1] or a + 3), then predict every line before you press Run.
#include <stdio.h>
void swap(int *x, int *y) {
int temp = *x;
*x = *y;
*y = temp;
}
int main(void) {
int a[5] = {10, 20, 30, 40, 50};
int *p = &a[2];
printf("*p = %d, it is element %d\n", *p, (int)(p - a));
printf("*(p - 1) = %d, *(p + 1) = %d\n", *(p - 1), *(p + 1));
*p = 99; /* change a[2] through p */
swap(&a[0], &a[4]); /* swap the first and last */
printf("Array now:");
for (p = a; p < a + 5; p++) /* walk the array with p */
printf(" %d", *p);
printf("\n");
return 0;
}Pro3 reviewers locked
Part of this subject's paid reviewers. Not included in this printout.
Done with this module? Track it — your progress shows on the subject list.
Studying with blockmates? Send them this lesson.
Up next
Lesson 4: Structures→←Previous: Lesson 2: Strings and Character Arrays