C Pointers
Pointers are much used in C, partly because they are sometimes the only way to express a computation, and partly because they usually lead to more compact and efficient code than can be obtained in other ways.
This post aims at a clear illustration of pointers and how to use it.
As you are familiar with variable that represents a specific value set aside in a memory location, and memory location has is own address in machine’s memory, in which an array of consecutively numbered or addressed memory cells that may be manipulated individually or in contiguous groups.
1. Address is numeric
The address is a numerical number (often expressed in hexadecimal). let’s print it out:
int a = 10;
prinf("Address of a is %X\n", &a); // EEBD089C
We use unary operator &
to get the address of an object, here the program print out the address of a: EEBD089C
.
We can of course save the address EEBD089C
to a int type variable, say addrA
:
int addrA = &a; // may give a warnning from compiler if you do this!
Save a address in such way is trivial since you can do nothing on the object set aside in that address. So we need a pointer points to that address to not only save address but also manipulate the object, directly.
2. Pointers and Addresses
int *pa; // declare `pa` as an int pointer
pa = &a; // pa now points to the address of a
*pa = 100; // change the value of a to 100
The unary operator *
is the indirection or dereferencing operator; when applied to a pointer, it accesses the object the pointer points to.
More example:
int x = 1, y = 2, z[10];
int *ip; // ip is a pointer to int
ip = &x; // ip now points to x
y = *ip; // y is now 1
*ip = 0; // x is now 0
ip = &z[0]; // ip now points to z[0]
You should also note the implication that a pointer is constrained to point to a particular kind of object: every pointer points to a specific data type. (There is one exception: a ‘‘pointer to void’’ is used to hold any type of pointer but cannot be dereferenced itself.)
Apply self increment or decrement operator ++/-- on both sides are difference, as unary operators like *
or ++/--
associate right to left:
++*ip; // increment whatever ip points to, as you expected
(*ip)++; // the parenthese is necessary!
3. Pointers and Function Arguments
Since C passes arguments to functions by value, there is no direct way for the called function to alter a variable in the calling function. For instance, you may want to swap two variables by writing a swap() function like this:
void swap(int a, int b) /* WRONG */
{
int tmp = a;
a = b;
b = tmp;
}
Because of call by value, swap has no way to access argument a
and b
in the routine that called it. The function above just swap copies of a and b.
The way to obtain the desired effect is for the calling program to pass pointers to the values to be changed:
void swap(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
int a = 10, b = 100;
swap(&a, &b);
printf("a = %d, b = %d\n",a,b); // a = 100, b = 10
Now by pointers arguments, the swap() works as you want. This is the scheme used by scanf() as well.
4. Pointers and Arrays
Pointers and arrays have strong relationships, strong enough that pointers and arrays should be discussed simulaneously:
int arr[10];
int *pa = &arr[0];
printf("Address of the 1st element: %p\n", pa); // 0x7ffeeb185870
printf("Let's print `arr` directly: %p\n", arr); // 0x7ffeeb185870
As you can see that arr
itself is also a pointer points to the first element. Thus we can also write pa
with arr
interchangeably:
pa = &arr[0]
pa = arr
// they are the same thing!
Now pa
points to the first element, by definition, pa + 1
points to the next element, pa + i
points to the i
elements after pa
, and pa - i
points to i
elements before. Thus *(pa+1)
refers to the points to arr[1]
.
These remarks are true regardless of the type or size of the variables in the array arr
. The meaning of ‘‘adding 1 to a pointer,’’ and by extension, all pointer arithmetic, is that pa + 1
points to the next object, and pa + i
points to the i-th object beyond pa
.
Rather more surprising, at first sight, is the fact that arr[i]
can also be written as pa[i]
. In evaluating arr[i]
or pa[i]
, C converts it to *(pa+i)
immediately.
NOTE: There is one difference between an array name and a pointer that must be kept in mind.
A pointer is a variable, sopa = arr
andpa++
are legal. But an array name is not a variable; constructions likearr = pa
andarr++
are illegal.
When an array name is passed to a function, what is passed is the location of the initial element. Within the called function, this argument is a local variable, and so an array name parameter is a pointer, that is, a variable containing an address:
/* strlen: return length of string s */
int strlen(char *s)
{
int n;
for (n = 0; *s != ’\0’, s++)
n++;
return n;
}
// All works
strlen("hello, world"); // string constant
strlen(array); // char array[100];
strlen(ptr); // char *ptr;
strlen(&array[50]); // or below
strlen(array+50); // are the same thing
As formal parameters in a function definition, char s[]
and char *s
are equivalent; we prefer the latter because it says more explicitly that the variable is a pointer.
5. Address Arithmetic
C is consistent and regular in its approach to address arithmetic; its integration of pointers, arrays, and address arithmetic is one of the strenghts of the language.
To illustrate it clear, I will use the simple example from The C Programming Language (written by K&RC)
Writing a simple storage allocator of characters. Two routines here:
alloc(n)
returns a pointer to the start ofn
consecutive character positionsafree(p)
releases the storage thus it can be re-used later
The standard library provides analogous functions called
malloc
andfree
Idea:
- An large character array
allocbuf
is privately shared byalloc
andafree
, and thus definedstatic
in a separate source file containingalloc
andafree
. - We also need to track that how much of
allocbuf
has been used. Therefore we need a pointerallocp
, that points to the next free element. Whenalloc
is asked forn
characters, it checks to see if there is enough room left inallocbuf
:- If it has enough memory,
alloc
returns the current value ofallocp
and increment it byn
to point to the next free area. - If it doesn’t, return 0
- If it has enough memory,
afree(p)
is simple, merely setsallocp
top
ifp
is insideallocbuf
- This example manage storage like a stack, which means
afree
must called in the opposite order to the calls ofalloc
/*
* Storage Allocator: ch_alloc.c
*/
#define ALLOCSIZE 10000 /* size of available space */
static char allocbuf[ALLOCSIZE]; /* storage for alloc */
static char *allocp = allocbuf; /* next free position */
char *alloc(int n) /* return pointer to n characters */
{
if (allocbuf + ALLOCSIZE - allocp >= n) { /* it fits */
allocp += n;
return allocp - n; /* old p */
} else /* not enough room */
return 0;
}
void afree(char *p) /* free storage pointed to by p */
{
if (p >= allocbuf && p < allocbuf + ALLOCSIZE)
allocp = p;
}
Let’s read off some information about pointers in the code above:
-
pointer is a variable, so we can declare it static as normal variable does:
static char *allop = allocbuf
initialize
allop
to the first element ofallocbuf
, which could also have writenstatic char *allop = &allocbuf[0]
since the array name is the address of the zero-th element.
-
allocbuf + ALLOCSIZE - allocp >= n
checks if there’s enough space ofn
consecutive characters. If not, return0
as a signal of no space left.NOTE: C guarantees that
0
is never a valid address for data. The const0
can be assigned to or compared with pointers. The symbolic constantNULL
is often used in palce of0
, as it’s more clear this is a special pointer. -
The above arithmetic expression also shows the fact that if two pointers
p
andq
point to members of the same array, then relations like==
,!=
,<
,>=
, etc., work properly, likeq < q
. -
We have already observed that a pointer
p
can be added or subtracted to an integern
, which means the result pointer will point ton
elements beyond or before the one thatp
currently points to:p + n // n elements beyond p
This is true regardless of the type of object
p
points to, asn
will be scaled according to the size of the objectp
points to. If anint
is 4 bytes, it’ll be scaled by 4.
Valid Pointer Operations:
- Assigning pointer of the same type
- Adding / subtracting a pointer with an integer
- Comparing two pointers of the same array
- Assigning / comparing to
0
orNULL
6. Character Pointers
Constructions like "Hello, world!"
is a string constant in C and we can easily store the string in a char []
array:
char [] hi = "Hello";
But one thing needs special attention that the array is terminated with a null character '\0'
, so that programs can find the end of the string, which means the above code is the same as:
char [] hi = {'H', 'e', 'l', 'l', 'o', '\0'};
We know that an array name itself is also an address of the first element, such that we can also write the pointer’s form interchangeablly as the array form above as:
char *phi = "Hello";
This form is usually used as function arguments, e.g. printf
.
NOTE: Since
phi
is a pointer points to the character array, it is not a string copy, and then could be changed to point to another address somewhere.
To make it more clear, see the example below. Two versions of function strcpy
, adapted from standard library <string.h>
:
/* strcpy: copy t to s; array subscript version */
void strcpy(char *s, char *t)
{
int i = 0;
while ((s[i] = t[i]) != '\0')
i++;
}
For contrast, here is a version of strcpy
with pointers:
/* strcpy: copy t to s; pointer version */
void strcpy(char *s, char *t)
{
while ((*s++ = *t++) != '\0') // a comparison against '\0' is redundant, could also written as
; // while (*s++ = *t++) ;
}
**NOTE:
*t++
is the character thatt
pointed to beforet
was incremented; the postfix++
doesn’t changet
until after this character has been fetched.
Because we take the advantage of the feature that function arguments passed by value, strcpy
uses s
and t
in any way it pleases. Some excercies in this section are worth doing…
–
Exercise 5-3. Write a pointer version of the function strcat that we showed in strcat(s,t)
copies the string t to the end of s.
/* strcat(s, t): concatenate t to end of s; s must be big enough */
void strcat(char *s, char *t)
{
while (*s) // find the end of s
++s;
while (*s++ = *t++) // concatenate t to s
;
}
–
Exercise 5-4. Write the function strend(s,t)
, which returns 1
if the string t
occurs at the end of the string s
, and 0
otherwise.
/* strend(s, t): returns 1 if s ends with t, otherwise returns 0 */
int strend(char *s, char *t)
{
// find the end of s
while (*s)
++s;
// find the end of t while count its length
int len;
for (len = 0; *t != '\0'; ++t, ++len)
;
// compare from end towards to begin
while (len > 0 && *--t == *--s)
--len;
// return the result
if (len == 0)
return 1;
else
return 0;
}
NOTE: Since
++
and--
are either postfix or prefix operators, combination like*--t
makes sense, although less frequently occur.
Standard idiom for pushing and poping a stack:*p++ = val; // push val onto stack val = *--p; // pop top of stack into val
7. Pointer Arrays
to be continued…