4 min read

On C Pointers and Arrays

In the C programming language, pointers and arrays have a strong relationship. For instance, any operation that uses array subscripting can also use pointers. Also, an array name is shorthand for &array[0]. (Another way to say it is that the array name decays to the first element of the array.) Whoa.

Let’s fire up vim and have a look.

I have an automatic command defined that will compile the current file and display the output, which makes development super-easy:

  autocmd FileType c nnoremap <leader>r :!clear && gcc % && ./a.out<cr>

I enact it by pressing <leader>r.

Incidentally, I have many of these “compile-on-the-fly” auto-commands defined for many different languages, and vim runs the proper one based on the file type. You can see them in my dotfiles repo.

Um, What is a Pointer?

Let’s remember that a pointer is simply a memory address. The size of a pointer is determined by the system architecture: 32-bit systems have pointers that are four bytes in length, and 64-bit systems have pointers that are eight bytes in length.

Pointers are aware of the size of the object that it points to, and so adding or subtracting by n will scale to the size of that data type. This is pointer arithmetic.

For instance, pointer p + n means the address of the nth address beyond the address that p points to. This is the case regardless of the type of object that p points to; n is scaled based on the data type.

For example:

int nums[] = { 1, 2, 3, 4, 5 };

// Points to the address of element 4, same as &nums[3].
// This is the same as moving ahead 12 bytes from the current address.
int *p = nums + 3;

/* --------------------------------------------- */

char s[] = "pete the dog";

// Points to the address of element 3, same as &s[2].
// This is the same as moving ahead 2 bytes from the current address.
char *p = s + 2;

Equivalencies

int primes[] = { 2, 3, 7, 11, 13, 17, 19, 23 };

// Memory addresses.
printf("%d\n", primes == &primes[0]);
printf("%d\n", primes + 2 == &primes[2]);

// Element values.
printf("%d\n", primes[0] == *primes);
printf("%d\n", primes[3] == *(primes + 3));

When evaluating primes[3], C converts it to *(primes + 3), so the two forms are the same.

Please Dennis, is there anything else I should know?

Let’s look at two alternatives of the strlen function that do the same thing, the only difference being how the character array elements are accessed:

// Array subscripting.
size_t strlen(char s[]) {
    size_t n;

    for (n = 0; s[n]; ++n)
        ;

    return n;
}

// Pointer arithmetic.
size_t strlen(char *s) {
    size_t n;

    for (n = 0; *(s + n) != '\0'; ++n)
        ;

    return n;
}

// Another one using pointer arithmetic and pointer subtraction.
size_t strlen(char *s) {
    char *p = s;

    while (*p != '\0')
        p++;

    return p - s;
}

The following function calls all produce the same results for both function definitions:

  1. String literal:

    strlen("hello world");
    
  2. Character array:

    char s[] = "hello world";
    strlen(s);
    
  3. Pointer to character array:

    char *s = "hello world";
    strlen(s);
    

Pointers will in general be faster, but some programmers, especially those coming from other languages, may prefer the familiarity of array subscripting.

In addition, the following function parameters are equivalent:

  • char s[]
  • char *s

C programmers tend to prefer the pointer notation because it is more explicit (that the parameter is a pointer).

Lastly, it’s possible to pass in a subarray of the array by passing in a pointer (the address of) to the beginning of the first element of the subarray:

strlen(&s[6]);
// or
strlen(s+6);

Pretty damn cool, if you ask me. Let’s just pretend that you did.

Conclusion

There is one significant difference between pointers and arrays: since a pointer is a variable, pa = array and pa++ are legal. However, array = pa and array++ are not, and will result in compiler errors like the following:

error: lvalue required as increment operand

References