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.
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
| Expression | Meaning |
|---|---|
int *p; | p can hold the address of an int |
p = &x; | p now points to x |
*p | the 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 |
NULL | a 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]ora[-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:
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)++
| Expression | What happens? |
|---|---|
*p++ | *(p++): read the value, then the pointer advances |
(*p)++ | the value pointed to increases by one, the pointer stays |
*++p | the pointer advances first, then read the new place |
++*p | the 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
| Expression | Value | Why |
|---|---|---|
strlen("hello") | 5 | counts up to the null |
sizeof(char s[] = "hello") | 6 | space including the null |
printf("%s", s + 2) | llo | from 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 == bcompares the addresses of the two arrays. Even if the contents are the same, the addresses differ, and the result is 0. For the contents usestrcmp(a, b) == 0.
char a[] = "abc", b[] = "abc";
printf("%d %d\n", a == b, strcmp(a, b) == 0);
0 1
Common mistakes
| Wrong idea | The truth |
|---|---|
int *p = &x; p = 9; changes x | It corrupts the address in p; write *p = 9 |
q = p copies the value | It copies the arrow, both point to the same place |
*p++ increments the value | It advances the pointer; use (*p)++ for the value |
| Passing an array to a function copies it | The address is passed, the original array changes |
&a[4] - &a[1] = 12 (bytes) | 3 elements |
| strlen(“hello”) = 6 | 5; sizeof is 6 |
a == b compares the string contents | It compares the addresses |
scanf("%d", x) | &x is needed |
Test yourself
Exam-style questions. Solve them on paper first, then reveal the answer.
What is printed?
int x = 5;
int *p = &x;
*p = 9;
printf("%d\n", x);Show answer
p holds the address of x. *p = 9 means 'write 9 where p points', that is, x = 9.
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
*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.
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
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.
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
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 +).
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
*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.
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
(*p)++ increments the VALUE pointed to (post-increment): x takes the old value (10), a[0] becomes 11. p still points to a[0].
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
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.
Given int a[6];, what is the value of &a[4] - &a[1]?
Show answer
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.
What is printed?
char s[] = "hello";
s[0] = 'j';
printf("%s %d %d\n", s, (int) strlen(s), (int) sizeof(s));Show answer
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.
What is printed?
char s[] = "hello";
char *p = s + 2;
printf("%s %c\n", p, *(p + 1));Show answer
p points to s[2]. %s prints from there up to the null: llo. *(p + 1) = s[3] = 'l'.
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
Swapping from the two ends toward the middle: (s,k), (t,c), the a in the middle stays in place. The result is kcats.
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
&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.