2.2. C’s Pointer Variables
C’s pointer variables provide a level of indirection to accessing program memory. By understanding how to use pointer variables, a programmer can write C programs that are both powerful and efficient. For example, through pointer variables, a C programmer can:
-
implement functions whose parameters can modify values in the caller’s stack frame
-
dynamically allocate (and deallocate) program memory at runtime when the program needs it
-
efficiently pass large data structures to functions
-
create linked dynamic data structures
-
interpret bytes of program memory in different ways.
In this section we introduce the syntax and semantics of C’s pointer variables and introduce common examples of how to use them in C programs.
2.2.1. Pointer Variables
A pointer variable stores the address of a memory location in which a value
of a specific type can be stored. For example, a pointer variable can store
the value of an int
address at which the integer value 12 is stored. The
pointer variable points to (refers to) the value. A pointer provides a
level of indirection for accessing values stored in memory. Figure 1
illustrates an example of what a pointer variable might look like in memory:
Through the pointer variable, ptr
, the value (12
) stored in the memory
location it points to can be indirectly accessed.
C programs most frequently use pointer variables for:
-
"Pass by pointer" parameters, for writing functions that can modify their argument’s value through a pointer parameter
-
Dynamic memory allocation, for writing programs that allocate (and free) space as the program runs. Dynamic memory is commonly used for dynamically allocating arrays. It is useful when a programmer doesn’t know the size of a data structure at compile time (e.g., the array size depends on user input at runtime). It also enables data structures to be resized as the program runs.
Rules for Using Pointer Variables
The rules for using pointer variables are similar to regular variables, except that you need to think about two types: the type of the pointer variable, and the type stored in the memory address to which the pointer variable points.
-
First, declare a pointer variable using
type_name *var_name
:int *ptr; // stores the memory address of an int (ptr "points to" an int) char *cptr; // stores the memory address of a char (cptr "points to" a char)
Pointer TypesNote that although
ptr
andcptr
are both pointers, they refer to different types:-
The type of
ptr
is "pointer to int" (int *
). It can point to a memory location that stores anint
value. -
The type of
cptr
is "pointer to char" (char *
). It can point to a memory location that stores achar
value.
-
-
Next, initialize the pointer variable (make it point to something). Pointer variables store address values. A pointer should be initialized to store the address of a memory location whose type matches the type to which the pointer variable points. One way to initialize a pointer is to use the address operator (
&
) with a variable to get the variable’s address value:int x; char ch; ptr = &x; // ptr gets the address of x, pointer "points to" x cptr = &ch; // cptr gets the address of ch, pointer "points to" ch
Figure 2. A program can initialize a pointer by assigning it the address of an existing variable of the appropriate type.Here’s an example of an invalid pointer initialization due to mismatched types:
cptr = &x; // ERROR: cptr can hold a char memory location // (&x is the address of an int)
Even though the C compiler may allow this type of assignment (with a warning about incompatible types), the behavior of accessing and modifying
x
throughcptr
will likely not behave as the programmer expects. Instead, the programmer should use anint *
variable to point to anint
storage location.All pointer variables can also be assigned a special value, NULL, which represents an invalid address. While a null pointer (one whose value is
NULL
) should never be used to access memory, the valueNULL
is useful for testing a pointer variable to see if it points to a valid memory address. That is, C programmers will commonly check a pointer to ensure that its value isn’tNULL
before attempting to access the memory location to which it points. To set a pointer toNULL
:ptr = NULL; cptr = NULL;
Figure 3. Any pointer can be given the special value NULL, which indicates that it doesn’t refer to any particular address. Null pointers should never be dereferenced.
-
Finally, use the pointer variable: the dereference operator (
*
) follows a pointer variable to the location in memory that it points to and accesses the value at that location:/* Assuming an integer named x has already been declared, this code sets the value of x to 8. */ ptr = &x; /* initialize ptr to the address of x (ptr points to variable x) */ *ptr = 8; /* the memory location ptr points to is assigned 8 */
Figure 4. Dereferencing a pointer accesses the value to which the pointer refers.
Pointer Examples
Here’s an example sequence of C statements using two pointer variables:
int *ptr1, *ptr2, x, y;
x = 8;
ptr2 = &x; // ptr2 is assigned the address of x
ptr1 = NULL;
*ptr2 = 10; // the memory location ptr2 points to is assigned 10
y = *ptr2 + 3; // y is assigned what ptr2 points to plus 3
ptr1 = ptr2; // ptr1 gets the address value stored in ptr2 (both point to x)
*ptr1 = 100;
ptr1 = &y; // change ptr1's value (change what it points to)
*ptr1 = 80;
When using pointer variables, carefully consider the types of the relevant
variables. Drawing pictures of memory (like those shown above) can help with
understanding what pointer code is doing. Some common errors involve misusing the
dereference operator (*
) or the address operator (&
). For example:
ptr = 20; // ERROR?: this assigns ptr to point to address 20
ptr = &x;
*ptr = 20; // CORRECT: this assigns 20 to the memory pointed to by ptr
If your program dereferences a pointer variable that does not contain a valid address, the program crashes:
ptr = NULL;
*ptr = 6; // CRASH! program crashes with a segfault (a memory fault)
ptr = 20;
*ptr = 6; // CRASH! segfault (20 is not a valid address)
ptr = x;
*ptr = 6; // likely CRASH or may set some memory location with 6
// (depends on the value of x which is used as an address value)
ptr = &x; // This is probably what the programmer intended
*ptr = 6;
These types of errors exemplify one reason to initialize pointer variables to
NULL
; a program can then test a pointer’s value for NULL
before
dereferencing it:
if (ptr != NULL) {
*ptr = 6;
}