Pointers and Arrays
Chapter Forty-One
Syllabus topic 3, "Pointer and Addresses, Pointer and Function Arguments, Pointer and Arrays."
Pages 199 to 204 of 222
In one line
In almost every expression an array's name yields the address of its first element, and a[i] is defined as *(a + i), which is why an array parameter is a pointer and why a function cannot find out how long an array is.
Pointer arithmetic
Adding an integer to a pointer moves it by that many elements, not bytes. The compiler knows the size of the type and does the multiplication.
#include <stdio.h>
int main(void)
{
int a[5] = {10, 20, 30, 40, 50};
int *p = &a[0];
printf("p points at %d\n", *p);
printf("*(p + 1) is %d\n", *(p + 1));
printf("*(p + 3) is %d\n", *(p + 3));
p++; /* moves forward by one int, 4 bytes */
printf("after p++, *p is %d\n", *p);
p += 2;
printf("after p += 2, *p is %d\n", *p);
int *q = &a[4];
printf("q - p is %ld element(s)\n", (long) (q - p));
printf("in bytes that is %ld\n", (long) ((char *) q - (char *) p));
return 0;
}p points at 10
*(p + 1) is 20
*(p + 3) is 40
after p++, *p is 20
after p += 2, *p is 40
q - p is 1 element(s)
in bytes that is 4p++ moved four bytes because p is an int . On a double it would move eight. That is the whole of pointer arithmetic: the unit is the type.
The identity
The language defines the subscript operator in terms of pointer arithmetic:
a[i] is *(a + i)and addition is commutative, so a[i], (a + i), (i + a) and even i[a] all mean the same thing.
#include <stdio.h>
int main(void)
{
int a[5] = {10, 20, 30, 40, 50};
printf("a[2] = %d\n", a[2]);
printf("*(a + 2) = %d\n", *(a + 2));
printf("*(2 + a) = %d\n", *(2 + a));
printf("2[a] = %d <- legal, and never write it\n", 2[a]);
printf("\nwalking with an index and with a pointer:\n");
for (int i = 0; i < 5; i++) {
printf(" a[%d] = %2d *(a + %d) = %2d\n", i, a[i], i, *(a + i));
}
return 0;
}a[2] = 30
*(a + 2) = 30
*(2 + a) = 30
2[a] = 30 <- legal, and never write it
walking with an index and with a pointer:
a[0] = 10 *(a + 0) = 10
a[1] = 20 *(a + 1) = 20
a[2] = 30 *(a + 2) = 30
a[3] = 40 *(a + 3) = 40
a[4] = 50 *(a + 4) = 502[a] is a curiosity and a favourite trick question. It is legal because it means *(2 + a), and you should never write it.
Pointers and Arrays
The identity also explains why indexes start at zero: a[0] is *(a + 0), which is the first element with no offset at all.
Array decay, and the three exceptions
In almost every expression, an array's name is converted to a pointer to its first element. That conversion is called decay, and it is why a can be passed to a function expecting int *.
The three places it does not happen:
sizeof a, which gives the size of the whole array.&a, which gives the address of the whole array, of type "pointer to array of 5 int".- A string literal initialising a character array,
char s[] = "Anita";, which copies the characters.
#include <stdio.h>
void by_parameter(int a[5]) /* looks like an array: it is a pointer */
{
printf(" inside, sizeof a is %zu <- the size of a POINTER\n", sizeof a);
printf(" inside, so sizeof a / sizeof a[0] is %zu, which is WRONG\n",
sizeof a / sizeof a[0]);
}
int main(void)
{
int a[5] = {10, 20, 30, 40, 50};
printf("in main, sizeof a is %zu <- the whole array\n", sizeof a);
printf("in main, sizeof a / sizeof a[0] is %zu, which is right\n",
sizeof a / sizeof a[0]);
printf("sizeof &a[0] is %zu, sizeof &a is %zu: both pointers\n",
sizeof &a[0], sizeof &a);
by_parameter(a);
return 0;
}gcc names this trap explicitly, which is worth reading twice:
decay.c: In function ‘by_parameter’:
decay.c:5:77: warning: ‘sizeof’ on array function parameter ‘a’ will return size of ‘int *’ [-Wsizeof-array-argument]
5 | printf(" inside, sizeof a is %zu <- the size of a POINTER\n", sizeof a);
| ^
decay.c:3:23: note: declared here
3 | void by_parameter(int a[5]) /* looks like an array: it is a pointer */
| ~~~~^~~~
decay.c:7:19: warning: ‘sizeof’ on array function parameter ‘a’ will return size of ‘int *’ [-Wsizeof-array-argument]
7 | sizeof a / sizeof a[0]);
| ^
decay.c:3:23: note: declared here
3 | void by_parameter(int a[5]) /* looks like an array: it is a pointer */
| ~~~~^~~~in main, sizeof a is 20 <- the whole array
in main, sizeof a / sizeof a[0] is 5, which is right
sizeof &a[0] is 8, sizeof &a is 8: both pointers
inside, sizeof a is 8 <- the size of a POINTER
inside, so sizeof a / sizeof a[0] is 2, which is WRONGThat is the answer to "why must I pass the length". Inside the function the parameter is a pointer, so sizeof gives 8 and the division gives 2, which is not the number of elements and is not an error the compiler reports. Chapter 36 said the sizeof trick works only where the array itself is in scope; this is the proof.
Pointers and Arrays
int a[5], int a[] and int a are the same parameter declaration. The 5 is documentation and the compiler ignores it. Write int a when you want a reader to know what it really is, and int a[] when you want them to know it is used as an array.
Where an array and a pointer genuinely differ
#include <stdio.h>
int main(void)
{
int a[5] = {10, 20, 30, 40, 50};
int *p = a; /* p points at a[0]; no & needed */
printf("a[1] is %d and p[1] is %d: subscripting works on both\n", a[1], p[1]);
p++; /* a pointer can be moved */
printf("after p++, p[0] is %d\n", p[0]);
p = a; /* and pointed somewhere else */
/* a++ would not compile: an array name is not a modifiable value */
printf("sizeof a is %zu, sizeof p is %zu\n", sizeof a, sizeof p);
printf("&a[0] == a is %d, and p == a is %d\n", &a[0] == a, p == a);
return 0;
}a[1] is 20 and p[1] is 20: subscripting works on both
after p++, p[0] is 20
sizeof a is 20, sizeof p is 8
&a[0] == a is 1, and p == a is 1Array int a[5] | Pointer int *p | |
|---|---|---|
| What it is | 5 objects | 1 object holding an address |
sizeof | 20 here | 8 here |
| Can be assigned | No | Yes |
| Can be incremented | No | Yes |
| Subscripting | Yes | Yes |
| Decays to a pointer | Yes, in most expressions | Already one |
| Memory it names | Its own | Somebody else's |
The last row is the one to remember. An array owns its memory; a pointer borrows somebody's.
Strings, finally explained
Chapter 37's whole library now makes sense. A string function takes a char *, and every call you made passed an array that decayed to one.
#include <stdio.h>
#include <string.h>
int main(void)
{
char word[] = "Anita";
char *p = word;
printf("the string is %s\n", word);
printf("through a pointer: %s\n", p);
printf("word[0] is %c and *p is %c\n", word[0], *p);
printf("the 4th character: word[3] is %c, *(p + 3) is %c\n",
word[3], *(p + 3));
printf("walking to the terminator with a pointer: ");
for (char *q = word; *q != '\0'; q++) {
printf("%c", *q);
}
printf("\n");
printf("strlen with pointer arithmetic: ");
char *end = word;
while (*end != '\0') { end++; }
printf("%ld, and strlen says %zu\n", (long) (end - word), strlen(word));
return 0;
}the string is Anita
through a pointer: Anita
word[0] is A and *p is A
the 4th character: word[3] is t, *(p + 3) is t
walking to the terminator with a pointer: Anita
strlen with pointer arithmetic: 5, and strlen says 5Pointers and Arrays
for (char q = word; q != '\0'; q++) is the idiomatic C loop over a string, and it is worth being able to read even if you write the indexed form. *q is the character, and the loop ends when it is the terminator.
An array of pointers, and a pointer to an array
Two declarations that look alike and are not.
int *a[5]; /* an array of 5 pointers to int */
int (*p)[5]; /* a pointer to an array of 5 int */[] binds tighter than , so int a[5] is an array first. The brackets in the second force the other reading. You will meet the first as an array of strings:
#include <stdio.h>
int main(void)
{
const char *names[] = {"Anita", "Rahul", "Fatima", "Joseph"};
int n = (int) (sizeof names / sizeof names[0]);
printf("%d name(s), each a pointer to a string literal:\n", n);
for (int i = 0; i < n; i++) {
printf(" names[%d] = %-8s (%zu characters)\n",
i, names[i], strlen(names[i]));
}
return 0;
}noheader.c: In function ‘main’:
noheader.c:11:29: warning: implicit declaration of function ‘strlen’ [-Wimplicit-function-declaration]
11 | i, names[i], strlen(names[i]));
| ^~~~~~
noheader.c:2:1: note: include ‘<string.h>’ or provide a declaration of ‘strlen’
1 | #include <stdio.h>
+++ |+#include <string.h>
2 |
noheader.c:11:29: warning: incompatible implicit declaration of built-in function ‘strlen’ [-Wbuiltin-declaration-mismatch]
11 | i, names[i], strlen(names[i]));
| ^~~~~~
noheader.c:11:29: note: include ‘<string.h>’ or provide a declaration of ‘strlen’4 name(s), each a pointer to a string literal:
names[0] = Anita (5 characters)
names[1] = Rahul (5 characters)
names[2] = Fatima (6 characters)
names[3] = Joseph (6 characters)That listing is missing #include <string.h>, and the warning is chapter 5's implicit declaration arriving again: gcc has never been told what strlen looks like, so it guesses, and the guess is wrong for a function returning size_t. It happened to print the right numbers here and that is luck, not correctness. The corrected version follows, and the only difference is the second #include.
#include <stdio.h>
#include <string.h>
int main(void)
{
const char *names[] = {"Anita", "Rahul", "Fatima", "Joseph"};
int n = (int) (sizeof names / sizeof names[0]);
printf("%d name(s), each a pointer to a string literal:\n", n);
for (int i = 0; i < n; i++) {
printf(" names[%d] = %-8s (%zu characters)\n",
i, names[i], strlen(names[i]));
}
return 0;
}4 name(s), each a pointer to a string literal:
names[0] = Anita (5 characters)
names[1] = Rahul (5 characters)
names[2] = Fatima (6 characters)
names[3] = Joseph (6 characters)const char *names[] is four pointers, each pointing at a literal somewhere in the program's read-only data. It is not four arrays of characters, which is why sizeof names is 32 on this machine and not the total length of the words.
Pointers and Arrays
What this does NOT mean
An array is not a pointer. It decays to one in most expressions, and sizeof and & show the difference.
A pointer is not an array. It has no elements of its own.
a[i] is not "a special array syntax". It is defined as *(a + i).
p + 1 is not "one byte further". It is one element further.
int a[5] as a parameter does not mean five elements. The compiler ignores the 5; the parameter is a pointer.
sizeof inside a function does not give the array's size. It gives the pointer's size, and no warning is issued.
char s and char s[] are not interchangeable as declarations of objects. As parameters they are the same; as local variables char s[] = "x" makes an array you may change and char s = "x" makes a pointer to a literal you may not.
Quick revision
- Pointer arithmetic counts in elements:
p + 1moves bysizeof(*p)bytes. a[i]is defined as(a + i), so(a + i),*(i + a)andi[a]all work.- Subtracting two pointers into the same array gives a count of elements.
- An array's name decays to a pointer to its first element in almost every expression.
- The three exceptions:
sizeof a,&a, and a string literal initialising achararray. - Inside a function, an array parameter is a pointer, so
sizeofgives 8 and the length must be passed. int a[5],int a[]andint *aare the same parameter declaration.- An array cannot be assigned or incremented; a pointer can.
for (char q = s; q; q++)is the idiomatic walk over a string.int a[5]is an array of pointers;int (p)[5]is a pointer to an array.
Test yourself
1. What is a[3] defined as?
*(a + 3).
2. If p is an int * and int is 4 bytes, how many bytes does p + 2 differ from p by?
Eight. Pointer arithmetic counts in elements.
3. Inside void f(int a[10]), what is sizeof a?
The size of a pointer, 8 on a 64-bit machine. The parameter is a pointer, whatever the brackets say.
4. Why must an array's length be passed to a function separately?
Because the array decays to a pointer, and a pointer carries no length. sizeof inside the function measures the pointer.
5. Give two things you can do with a pointer that you cannot do with an array name.
Assign to it, and increment it.
6. What is the difference between char s[] = "hi"; and char *s = "hi";?
Pointers and Arrays
The first is an array of three characters copied into memory you own and may change. The second is a pointer to a string literal, which you must not change.
7. What does int *a[5] declare?
An array of five pointers to int. A pointer to an array of five int would be int (*a)[5].
What can be asked on this, and how to answer it
"Explain the relationship between pointers and arrays." State the identity a[i] is *(a + i), say that an array's name decays to a pointer to its first element in most expressions, and give the three exceptions. Then the consequence: an array parameter is a pointer, so the length must be passed.
"What is pointer arithmetic?" Adding an integer to a pointer moves it by that many elements, using the size of the pointed-to type; subtracting two pointers into the same array gives the number of elements between them. Give a worked example with sizeof(int) and the byte difference.
"Distinguish between an array and a pointer." Give the table: an array is several objects and owns its memory, a pointer is one object holding an address; sizeof differs; an array cannot be assigned or incremented; both can be subscripted; an array decays to a pointer.
"Write a program to print the elements of an array using a pointer." Give a loop with *(a + i) or with a moving pointer, and say in one line that a[i] is the same thing written differently.
"Why does sizeof give the wrong answer inside a function?" Because the parameter is a pointer, not an array: the array decayed at the call. sizeof therefore measures the pointer, and it does so silently, which is why the length is a separate parameter in every C library function that takes an array.
The rest of this subject
These notes are cut from the University's printed syllabus. Open the syllabus itself for the same subject.