munotes®

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 4

p++ 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) = 50

2[a] is a curiosity and a favourite trick question. It is legal because it means *(2 + a), and you should never write it.

munotes.in199

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:

  1. sizeof a, which gives the size of the whole array.
  2. &a, which gives the address of the whole array, of type "pointer to array of 5 int".
  3. 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 WRONG

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

munotes.in200

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 1
Array int a[5]Pointer int *p
What it is5 objects1 object holding an address
sizeof20 here8 here
Can be assignedNoYes
Can be incrementedNoYes
SubscriptingYesYes
Decays to a pointerYes, in most expressionsAlready one
Memory it namesIts ownSomebody 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 5
munotes.in201

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

munotes.in202

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 + 1 moves by sizeof(*p) bytes.
  • a[i] is defined as (a + i), so (a + i), *(i + a) and i[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 a char array.
  • Inside a function, an array parameter is a pointer, so sizeof gives 8 and the length must be passed.
  • int a[5], int a[] and int *a are 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";?

munotes.in203

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.

munotes.in204

The rest of this subject

These notes are cut from the University's printed syllabus. Open the syllabus itself for the same subject.

Report or request
Done!