SEN2201 · Week 13 · Programming in C

Pointers, arrays and strings in C

The address (&) and content (*) operators, pointer diagrams, swapping with pointers, arrays and pointer arithmetic, passing an array to a function, null-terminated strings and the LC-3 memory layout.

pointeraddress operator (&)indirection / dereference (*)NULLcall by referencearrayindexpointer arithmeticarray name as pointerstringnull terminatorstrlenstrcmpbuffer overflow

Pointers are the topic most feared when learning C. Yet in this course it is something you already know: the address. When you wrote LDR R1, R2, #0 on the LC-3, R2 was a pointer; it held not a value but a memory address. The *p in C is exactly this. This page explains pointers, arrays and strings together, because in C the three are parts of the same idea.

Exam questions: “What is printed?” by drawing a pointer diagram, array and pointer arithmetic, string processing functions, LC-3 memory address calculation.

Address and pointer

Every variable is at a location in memory. The & operator gives the address of that location. A pointer variable is a variable that stores an address.

int x = 42;
int *p = &x;        /* p holds the address of x */
printf("%d %d\n", *p, p == &x);
*p = *p + 8;        /* x = 50 */
printf("%d\n", x);
42 1
50
ExpressionMeaning
int *p;p can hold the address of an int
p = &x;p now points to x
*pthe value at the place p points to (dereference)
*p = 9;write 9 to the place pointed to
p = q;p also points to the place q points to
NULLa pointer that points nowhere

* is used in two senses: in a declaration (int *p) it means “pointer type”, in an expression (*p) “the value pointed to”.

Pointer diagram

In every question, draw boxes and arrows. Box = variable, arrow = pointer.

int a = 3, b = 7;  int *p = &a, *q = &b;

 p ──► [a: 3]        q ──► [b: 7]

*p = *q + 1;        p ──► [a: 8]        q ──► [b: 7]
q = p;              p ──► [a: 8] ◄── q        [b: 7]
*q = *q * 2;        p ──► [a: 16] ◄── q       [b: 7]

The result: a = 16, b = 7. The line q = p does not copy the value, it copies the arrow.

The LC-3 counterpart

Let x be a local variable (R5 + 0) and p the second local (R5 - 1):

        ADD  R0, R5, #0     ; R0 = &x
        STR  R0, R5, #-1    ; p = &x
        LDR  R1, R5, #-1    ; R1 = p
        LDR  R2, R1, #0     ; R2 = *p

&x is an address calculation (ADD or LEA), *p is an LDR.

“Call by reference” with pointers

In C everything is passed by value. But if the value is an address, the function can change the original variable at that address.

void swap(int *a, int *b) { int t = *a; *a = *b; *b = t; }

int x = 1, y = 2;
swap(&x, &y);           /* x = 2, y = 1 */

This is also the way to return more than one result from a function:

void minmax(int a[], int n, int *mn, int *mx) {
    int i;
    *mn = *mx = a[0];
    for (i = 1; i < n; i++) {
        if (a[i] < *mn) *mn = a[i];
        if (a[i] > *mx) *mx = a[i];
    }
}

int a[5] = {4, -1, 9, 3, 0}, mn, mx;
minmax(a, 5, &mn, &mx);
printf("%d %d\n", mn, mx);
-1 9

The & in the line scanf("%d", &x); is there for this reason: scanf writes the number it reads to the address of x.

Arrays

int a[5]; reserves 5 ints in a row in memory. The index starts at 0: a[0] … a[4].

On the LC-3 (int = one word), if a starts at x4010:

address: x4010  x4011  x4012  x4013  x4014
        [a[0]] [a[1]] [a[2]] [a[3]] [a[4]]

The address of a[i] = base + i. If the array is local it sits on the stack, if global in the global region.

PITFALL · no bounds checking. C does not stop you from writing a[5] or a[-1]; it just reads or overwrites the memory at that address. On the stack this can corrupt another variable or the return address: the classic source of security holes (buffer overflow).

The array name is an address

a is the same as &a[0]. Therefore:

a[i]≡∗(a+i)p[i]≡∗(p+i)a[i] \equiv *(a + i) \qquad p[i] \equiv *(p + i)

Pointer arithmetic: p + k points k elements ahead; the address increases by the size of the type. The difference of two pointers is the number of elements in between.

int a[4] = {7, 3, 9, 1};
int *p;
int s = 0;
for (p = a; p < a + 4; p++)
    s += *p;
printf("%d\n", s);
20

*p++ versus (*p)++

ExpressionWhat happens?
*p++*(p++): read the value, then the pointer advances
(*p)++the value pointed to increases by one, the pointer stays
*++pthe pointer advances first, then read the new place
++*pthe value increases, the increased value is read

Passing an array to a function

The array is not copied; the address of the first element is passed. void f(int v[]) and void f(int *v) are the same. The function cannot know the size of the array, so the size is given as a separate parameter.

Strings

In C a string is a char array with a null character ('\0', value 0) at the end. The same idea as .STRINGZ on the LC-3.

char s[] = "Hi";
printf("%d %d %d\n", s[0], s[1], s[2]);
72 105 0
ExpressionValueWhy
strlen("hello")5counts up to the null
sizeof(char s[] = "hello")6space including the null
printf("%s", s + 2)llofrom s[2] to the null

Your own string functions

int mylen(const char *s) {
    int n = 0;
    while (s[n] != '\0') n++;
    return n;
}

void mycopy(char *dst, const char *src) {
    while ((*dst++ = *src++) != '\0')
        ;
}

int countc(const char *s, char c) {
    int k = 0;
    for (; *s; s++)
        if (*s == c) k++;
    return k;
}
char d[20];
mycopy(d, "banana");
printf("%s %d\n", d, countc(d, 'a'));
banana 3

PITFALL · comparing strings with ==. a == b compares the addresses of the two arrays. Even if the contents are the same, the addresses differ, and the result is 0. For the contents use strcmp(a, b) == 0.

char a[] = "abc", b[] = "abc";
printf("%d %d\n", a == b, strcmp(a, b) == 0);
0 1

Common mistakes

Wrong ideaThe truth
int *p = &x; p = 9; changes xIt corrupts the address in p; write *p = 9
q = p copies the valueIt copies the arrow, both point to the same place
*p++ increments the valueIt advances the pointer; use (*p)++ for the value
Passing an array to a function copies itThe address is passed, the original array changes
&a[4] - &a[1] = 12 (bytes)3 elements
strlen(“hello”) = 65; sizeof is 6
a == b compares the string contentsIt compares the addresses
scanf("%d", x)&x is needed

Test yourself

Exam-style questions. Solve them on paper first, then reveal the answer.

Question 1easy

What is printed?

int x = 5;
int *p = &x;
*p = 9;
printf("%d\n", x);
Show answer
9

p holds the address of x. *p = 9 means 'write 9 where p points', that is, x = 9.

Question 2medium

What is printed?

int a = 3, b = 7;
int *p = &a, *q = &b;
*p = *q + 1;
q = p;
*q = *q * 2;
printf("%d %d\n", a, b);
Show answer
16 7

*p = *q + 1 → a = 7 + 1 = 8. q = p → q now points to a (not b). *q = *q * 2 → a = 16. b never changed: 7.
Draw a diagram: at each step update where the arrows point.

Question 3easy

What is printed?

void swap(int *a, int *b) { int t = *a; *a = *b; *b = t; }
/* in main: */
int x = 1, y = 2;
swap(&x, &y);
printf("%d %d\n", x, y);
Show answer
2 1

This time the addresses of x and y are passed. swap changes the values at the addresses. Call by value still applies (the addresses are copied), but since the copied addresses point to the same places, the original variables change.

Question 4medium

What is printed?

int a[5] = {3, 1, 4, 1, 5};
int *p = a;
printf("%d %d %d\n", *(p + 2), p[4], *a + 1);
Show answer
4 5 4

The array name is the address of the first element: p = &a[0]. *(p + 2) = a[2] = 4. p[4] = *(p + 4) = 5. *a + 1: first *a = 3, then + 1 = 4 (the precedence of * is higher than +).

Question 5hard

What is printed?

int a[] = {10, 20, 30};
int *p = a;
int x = *p++;
printf("%d %d %d\n", x, *p, a[0]);
Show answer
10 20 10

*p++ = *(p++): the old value of p is used (x = a[0] = 10), then the POINTER advances by one element. *p = a[1] = 20. The array did not change.

Question 6hard

What is printed?

int a[] = {10, 20, 30};
int *p = a;
int x = (*p)++;
printf("%d %d %d\n", x, *p, a[0]);
Show answer
10 11 11

(*p)++ increments the VALUE pointed to (post-increment): x takes the old value (10), a[0] becomes 11. p still points to a[0].

Question 7medium

What is printed?

void dbl(int v[], int n) { int i; for (i = 0; i < n; i++) v[i] *= 2; }
/* in main: */
int v[3] = {1, 2, 3};
dbl(v, 3);
printf("%d %d %d\n", v[0], v[1], v[2]);
Show answer
2 4 6

The array is not copied to the function; the address of the first element is passed (int v[] is really int *v). The function changes the original array.

Question 8medium

Given int a[6];, what is the value of &a[4] - &a[1]?

Show answer
3

Pointer subtraction gives the number of ELEMENTS in between (not bytes). 4 - 1 = 3. Pointer arithmetic automatically takes the size of the type into account.

Question 9medium

What is printed?

char s[] = "hello";
s[0] = 'j';
printf("%s %d %d\n", s, (int) strlen(s), (int) sizeof(s));
Show answer
jello 5 6

s is a char array and can be modified. strlen counts up to the null ('\0'): 5. sizeof is the space the array takes in memory: 5 characters + 1 null = 6.

Question 10medium

What is printed?

char s[] = "hello";
char *p = s + 2;
printf("%s %c\n", p, *(p + 1));
Show answer
llo l

p points to s[2]. %s prints from there up to the null: llo. *(p + 1) = s[3] = 'l'.

Question 11medium

What is printed?

void reverse(char s[]) {
    int i = 0, j = strlen(s) - 1;
    while (i < j) {
        char t = s[i]; s[i] = s[j]; s[j] = t;
        i++; j--;
    }
}
/* in main: */
char w[] = "stack";
reverse(w);
printf("%s\n", w);
Show answer
kcats

Swapping from the two ends toward the middle: (s,k), (t,c), the a in the middle stays in place. The result is kcats.

Question 12hard

On the LC-3 (an int takes one 16-bit memory location), int a[4] starts at address x4010 and int *p = &a[1];. What address does p + 2 hold, and which element is it?

Show answer
x4013, a[3]

&a[1] = x4010 + 1 = x4011. p + 2 = x4011 + 2 = x4013 = &a[3]. Because the LC-3 is word-addressable and an int is one word, each element advances by one address. On a byte-addressable 32-bit machine the step would be 4 bytes.

Verification: All code snippets were compiled and run as C99 with Zig cc (clang), and the outputs were copied exactly (verify/SEN2201/c/w13.c, node verify/SEN2201/c/run.mjs w13). The LC-3 address questions were computed by hand assuming a one-word int.

Review card

&x = the ADDRESS of x | *p = the VALUE p points to | int *p = &x; *p = 9 -> x = 9 | p = q -> p points to what q points to (the value does not change) swap(&x, &y) swaps: pass addresses, change with *a scanf("%d", &x): wants an address Array name = &a[0] | a[i] == *(a + i) == p[i] p + k -> k ELEMENTS forward | &a[4] - &a[1] = 3 (number of elements) *p++ -> take the value, the POINTER advances | (*p)++ -> the VALUE increases Passing an array to a function = passing an address -> the original array changes String = char array + '\0' | "hello": strlen 5, sizeof 6 %s prints up to the null | s + 2 -> "llo" a == b on strings compares ADDRESSES -> use strcmp(a,b) == 0 LC-3: int = 1 word, &a[i] = base + i | no bounds checking (buffer overflow)