C Programming: What is the difference between an array and a pointer?
Why is a raven like a writing-desk?
(Lewis Carroll)
This is a copy of an article I wrote a long time ago. I'm putting it here to give it a more permanent home. Sorry for being off topic again!
Introduction
I'm glad you asked. The answer is surprisingly simple: almost everything. In other words, they have almost nothing in common. To understand why, we'll take a look at what they are and what operations they support.
Arrays
An array is a fixed-length collection of objects, which are stored sequentially in memory. There are only three things you can do with an array:
sizeof
- get its size
You can applysizeof
to it. An array x of N elements of type T (T x[N]
) has the sizeN * sizeof (T)
, which is what you should expect. For example, ifsizeof (int) == 2
andint arr[5];
, thensizeof arr == 10 == 5 * 2 == 5 * sizeof (int)
.&
- get its address
You can take its address with&
, which results in a pointer to the entire array.- any other use - implicit pointer conversion
Any other use of an array results in a pointer to the first array element (the array "decays" to a pointer).
That's all. Yes, this means arrays don't provide direct access to their contents. More specifically, there is no array indexing operator.
Pointers
A pointer is a value that refers to another object (or function). You might say it contains the object's address. Here are the operations that pointers support:
sizeof
- get its size
Like arrays, pointers have a size that can be obtained withsizeof
. Note that different pointer types can have different sizes.&
- get its address
Assuming your pointer is an lvalue, you can take its address with&
. The result is a pointer to a pointer.*
- dereference it
Assuming the base type of your pointer isn't an incomplete type, you can dereference it; i.e., you can follow the pointer and get the object it refers to. Incomplete types includevoid
and predeclaredstruct
types that haven't been defined yet.+
,-
- pointer arithmetic
If you have a pointer to an array element, you can add an integer amount to it. This amount can be negative, andptr - n
is equivalent toptr + -n
(and-n + ptr
, since+
is commutative, even with pointers). Ifptr
is a pointer to thei
'th element of an array, thenptr + n
is a pointer to the(i + n)
'th array element, unlessi + n
is negative or greater than the number of array elements, in which case the results are undefined. Ifi + n
is equal to the number of elements, the result is a pointer that must not be dereferenced.
That's it, really. However, there are a few other pointer operations defined in terms of the above fundamental operations:
->
- struct dereference
p->m
is equivalent to(*p).m
, where.
is the struct/union member access operator. This meansp
must be a pointer to a struct or union.[]
- indexed dereference
a[b]
is equivalent to*(a + b)
. This meansa
andb
must be a pointer to an array element and an integer; not necessarily respectively, becausea[b] == *(a + b) == *(b + a) == b[a]
. Another important equivalence isp[0] == 0[p] == *p
.
A quirk of parameter declarations
However, there's one thing that confuses this issue. Whenever you declare a function parameter to have an array type, it gets silently converted to a pointer and any size information is ignored. Thus the following four declarations are equivalent:
void foo(int [42]);
void foo(int []);
typedef int t_array[23];
void foo(t_array);
void foo(int *);
A more common example is int main(int argc, char *argv[])
, which is the same
as int main(int argc, char **argv)
. However,
int main(int argc, char argv[][])
would be an error because the above rule
isn't recursive; the result after conversion would be
int main(int argc, char (*argv)[])
, i.e. argv
would be a pointer to an
array of unknown size, not a pointer to a pointer.
Conclusion
Arrays by themselves are nearly useless in C. Even the fundamental []
operator, which is used for getting at the array's contents, is an illusion:
it's defined on pointers and only happens to work with arrays because of the
rule that any use of an array outside of sizeof
and &
yields a pointer.
Yes, pointers in C will drive you batty. Perl's references are much easier to understand.
Note that there's an interesting parallel between C and Perl when it comes to arrays.
Neither C nor Perl have array values (that is, values that represent arrays). When you evaluate an array in C, you get a pointer to its first element. When you evaluate an array in Perl, you get a count (in scalar context) or a list of elements (in list context).
Both languages let you avoid this "decay" by taking the address of (&, C) or a reference to (\, Perl) the array.
You should put more emphasis on
sizeof
for pointers: the pointer has no idea of how many elements it points to.Well, that's because (with the exception of VLAs in C99)
sizeof
is purely a compile time operation. It only looks at the types:sizeof EXPR == sizeof (typeof(EXPR))
(if we pretend C hadtypeof
for a moment).