Sams Teach Yourself C in 24 Hours

In Hour 11, "An Introduction to Pointers," you learned the basics of
using pointers in C. Because pointers are very useful in programming, it's
worth spending another hour to learn more about them. In this lesson, the
following topics are discussed:

In C, you can move the position of a pointer by adding or subtracting
integers to or from the pointer. For example, given a character pointer variable
ptr_str, the following expression

ptr_str + 1

indicates to the compiler to move to the memory location that is one byte away from
the current position of ptr_str.

Note that for pointers of different data types, the integers added to or subtracted from
the pointers have different scalar sizes. In other words, adding 1 to (or subtracting 1 from)
a pointer is not instructing the compiler to add (or subtract) one byte to the address, but
to adjust the address so that it skips over one element of the type of the pointer. You'll
see more details in the following sections.

The following output is obtained by running the executable,
16L01.exe, of the program in Listing 16.1 on my machine. You might get a different address on your computer, but
the offsets should remain the same:

OUTPUT

C:\app>16L01
Current position of ptr_ch: 0x000B
The position after ptr_ch + 1: 0x000C
The position after ptr_ch + 2: 0x000D
The position after ptr_ch - 1: 0x000A
The position after ptr_ch - 2: 0x0009
Current position of ptr_int: 0x028B
The position after ptr_int + 1: 0x028D
The position after ptr_int + 2: 0x028F
The position after ptr_int - 1: 0x0289
The position after ptr_int - 2: 0x0287
Current position of ptr_db: 0x0128
The position after ptr_db + 1: 0x0130
The position after ptr_db + 2: 0x0138
The position after ptr_db - 1: 0x0120
The position after ptr_db - 2: 0x0118
C:\app>

ANALYSIS

As you can see in Listing 16.1, there are three types of
pointersptr_ch, ptr_int, and ptr_dbdeclared in lines
6_8. Among them, ptr_ch is a pointer to a character,
ptr_int is a pointer to an integer, and ptr_db is a pointer to a double.

The statement in line 10 shows the memory address,
0x000B, contained by the char pointer variable
ptr_ch. Lines 11 and 12 display the two addresses,
0x000C and 0x000D, when ptr_ch is added to 1 and 2, respectively. Similarly, lines 13 and 14 give
0x000A and 0x0009 when ptr_ch is moved down to lower memory addresses. Because the size of
char is 1 byte, ptr_ch+1 means to move to the memory location that is 1 byte higher than the current memory
location, 0x000B, referenced by the pointer ptr_ch.

Line 16 shows the memory location referenced by the int pointer variable ptr_int
at 0x028B. Because the size of int is 2 bytes long, the ptr_int+1 expression
simply means to move to the memory location that is 2 bytes higher than the
current one pointed to by ptr_int. That's exactly what has been printed out
in line 17. Likewise, line 18 shows that ptr_int+2 causes the reference to be
moved to 0x028F, which is 4 bytes higher than 0x028B. The memory location of
0x0289 is referenced by the ptr_int-1 expression in line 19; 0x0287 is referenced
by ptr_int-2 in line 20.

The size of the double data type is 8 bytes long. Therefore, the
ptr_db+1 expression is interpreted as the memory address referenced by
ptr_db plus 8 bytesthat is, 0x0128+8, which gives
0x0130 in hex format. (See the output made by the statement in line 23.)

Lines 24_26 print out the memory addresses referenced by
ptr_db+2, ptr_db-1, and
ptr_db-2, respectively, which prove that the compiler has taken the scalar size of
double in the pointer arithmetic.

WARNING

Pointers are useful if you use them properly. On the other hand, a pointer
can get you into trouble if it contains a wrong value. A common error, for instance,
is to assign a right value to a pointer that actually expects a left one.
Fortunately, many C compilers can find such an error and issue a warning message.
There is another common error that the compiler does not pick up for you:
using uninitialized pointers. For example, the following code has a potential problem:

int x, ptr_int;
x = 8;
*ptr_int = x;

The problem is that the ptr_int pointer is not initialized; it points to
some unknown memory location. Therefore, assigning a value, like
8 in this case, to an unknown memory location is dangerous. It may overwrite some important
data that is already saved at the memory location, thus causing a serious
problem. The solution is to make sure that a pointer is pointing at a legal and
valid memory location before it is used.
You can rewrite this C code to avoid the potential problem like this:

For two pointers of the same type, you can subtract one pointer value from the other.
For instance, given two char pointer variables,
ptr_str1 and ptr_str2, you can calculate the
offset between the two memory locations pointed to by the two pointers like this:

ptr_str2 - ptr_str1

However, it's illegal in C to subtract one pointer value from another if they do not
share the same data type.

Listing 16.2 gives an example of performing subtraction on an
int pointer variable.

After running the executable (16L02.exe) of the program in Listing 16.2 on my machine,
I have the following output shown on the screen:

OUTPUT

C:\app>16L02
The position of ptr_int1: 0x0128
The position of ptr_int2 = ptr_int1 + 5: 0x0132
The subtraction of ptr_int2 - ptr_int1: 5
The position of ptr_int2 = ptr_int1 - 5: 0x011E
The subtraction of ptr_int2 - ptr_int1: -5
C:\app>

ANALYSIS

The program in Listing 16.2 declares two int pointer variables,
ptr_int1 and ptr_int2, in line 6. The statement in line 8 prints out the memory position held by
ptr_int1. Line 9 assigns the memory address referenced by
ptr_int1+5 to ptr_int2. Then, the content of
ptr_int2 is printed out in line 10.

The statement in line 11 shows the difference between the two
int pointersthat is, the subtraction of
ptr_int2 and ptr_int1. The result is 5.

Line 12 then assigns another memory address, referenced by the
ptr_int1-5 expression, to the ptr_int2 pointer. Now,
ptr_int2 points to a memory location that is 10 bytes lower than
the memory location pointed to by ptr_int1 (see the output made by line 13.) The
difference between ptr_int2 and ptr_int1 is obtained by the subtraction of the two pointers, which is
-5 as printed out by the statement in line 14.

As indicated in previous lessons, pointers and arrays have a close relationship. You
can access an array through a pointer that contains the start address of the array. The
following subsection introduces how to access array elements through pointers.

Because an array name that is not followed by a subscript is interpreted as a pointer to
the first element of the array, you can assign the start address of the array to a pointer of
the same data type; then you can access any element in the array by adding a proper
integer to the pointer. The value of the integer is the same as the subscript value of the
element that you want to access.

In other words, given an array, array, and a pointer,
ptr_array, if array and ptr_array are of the same data type, and
ptr_array contains the start address of the array, that is

ptr_array = array;

then the expression array[n] is equivalent to the expression

*(ptr_array + n)

Here n is a subscript number in the array.

Listing 16.3 demonstrates how to access arrays and change values of array elements
by using pointers.

The following output is displayed on the screen after the executable,
16L03.exe, is created and run from a DOS prompt:

OUTPUT

C:\app>16L03
Before the change, str contains: It's a string!
Before the change, str[5] contains: a
After the change, str[5] contains: A
After the change, str contains: It's A string!
Before the change, list[2] contains: 3
After the change, list[2] contains: -3
C:\app>

ANALYSIS

The purpose of the program in Listing 16.3 is to access a
char array, str, and an int array, list. In lines 6 and 8,
str and list are declared and initialized with a string
and a set of integers, respectively. A char pointer,
ptr_str, and an int pointer, ptr_int, are declared in lines 7 and 9.

Line 12 assigns the start address of the str array to the
ptr_str pointer. The statements in lines 13 and 14 demonstrate the content of the string saved in the
str array, as well as the character contained by the
str[5] element in the array before any changes are made to
str.

The statement in line 15 shows that the character constant,
`A', is assigned to the element of the str array pointed to by the expression

*(ptr_str + 5)

To verify that the content of the element in
str has been updated, lines 16 and 17 print out the element and the whole string, respectively. The output indicates that
`A' has replaced the original character constant,
`a'.

The start address of the int array list is assigned to the
ptr_int pointer in line 19. Before I do anything with the
list[2] element of the list array, I print out its value, which is
3 at this moment (see the output made by line 20). In line 21, the
list[2] element is given another value, -3, through the dereferenced pointer,
*(ptr_int + 2). The printf() function in line 22 prints the
latest value of list[2].

In practice, it's usually awkward if you pass more than five or six arguments to a
function. One way to save the number of arguments passed to a function is to use arrays. You
can put all variables of the same type into an array, and then pass the array as a single argument.

The program in Listing 16.4 shows how to pass an array of integers to a function.

The following output is obtained after I run the executable,
16L04.exe, and enter three integers, 10, 20, and
30, from a DOS prompt:

OUTPUT

C:\app>16L04
Enter three integers separated by spaces:
10 20 30
The sum of the three integers is: 60
C:\app>

ANALYSIS

The purpose of the program in Listing 16.4 is to obtain three integers entered
by the user, and then pass the three integers as an array to a function called
AddThree() to perform the operation of addition.

Line 4 gives the declaration of the AddThree()
function. Note that the unsized array, list[], is used in the argument expression, which indicates that the argument contains the
start address of the list array.

The list array and an integer variable, sum, are declared in line 8. The
printf() function in line 10 displays a message asking the user to enter three integers. Then, line 11 fetches
the integers entered by the user and stores them in the three memory locations of the
elements in the integer array referenced by
&list[0], &list[1], and &list[2], respectively.

The statement in line 12 calls the AddThree() function with the name of the array as
the argument. The AddThree(list) expression is actually passing the start address of the
list array to the AddThree() function.

The definition of the AddThree() function is in lines 18_26; it adds the values of all
three elements in the list array and returns the sum. The result returned from the
AddThree() function is assigned to the integer variable
sum in line 12 and is printed out in line 13.

NOTE

You can also specify the size of an array that is passed to a function.
For instance, the following

function(char str[16]);

is equivalent to the following statement:

function(char str[]);

Remember that the compiler can figure out the size for the unsized array
str[].
For multidimensional arrays, the format of an unsized array should always
be used in the declaration. (See the section titled "Passing Multidimensional
Arrays as Arguments," later in this hour.)

As you know, an array name that is not followed by a subscript is interpreted as a
pointer to the first element of the array. In fact, the address of the first element in an array is
the start address of the array. Therefore, you can assign the start address of an array to
a pointer, and then pass the pointer name, instead of the unsized array, to a function.

Listing 16.5 gives an example of passing pointers to functions, which is similar to
the situation in which arrays are passed to functions.

After executing the 16L05.exe program, the following output is displayed on the screen:

OUTPUT

C:\app>16L05
It's a string!
It's a string!
The sum returned by DataAdd(): 15
The sum returned by DataAdd(): 15
C:\app>

ANALYSIS

The purpose of the program in Listing 16.5 is to demonstrate how to pass
an integer pointer that points to an integer array and a character pointer
that references a character string to two functions that are declared in lines 4 and 5.

Note that expressions, such as char *ch and int
*list, are used as arguments in the
function declarations, which indicates to the compiler that a
char pointer and an int pointer are respectively passed to the functions
ChPrint() and DataAdd().

Inside the main() function body, lines 8 and 9 declare a
char array (str) that is initialized with a character string, and a
char pointer variable (ptr_str). Line 10 declares and initializes an
int array (list) with a set of integers. An int pointer variable,
ptr_int, is declared in line 11.

The start address of the str array is assigned to the
ptr_str pointer by the assignment statement in line 14. Then, the
ptr_str pointer is passed to the ChPrint() function as the argument in
line 15. According to the definition of ChPrint() in lines 27_30, the content of the
str array whose start address is passed to the function as the argument is printed out by the
printf() function that is invoked inside the
ChPrint() function in line 29.

In fact, you can still use the name of the str array as the argument and pass it to the
ChPrint() function. Line 16 shows that the start address of the character array is passed to
ChPrint() via the name of the array.

The statement in line 19 assigns the start address of the integer array
list to the integer pointer ptr_int. Then, the
ptr_int pointer is passed to the DataAdd() function in line 21,
along with 5, which is the maximum number of the elements contained by the
list array. From the definition of the DataAdd()
function in lines 32_40, you can see that
DataAdd() adds all the integer elements in list and returns the sum to the caller. Thereafter, the statement in
lines 20 and 21 prints out the result returned from
DataAdd().

The expression in line 23 also invokes the
DataAdd() function, but this time, the name of the
list array is used as the argument to the function. Not surprisingly, the start address of
the list array is passed to the DataAdd() function successfully, and the
printf() statement in lines 22 and 23 displays the right result on the screen.

In Hour 12, "Storing Similar Data Items," you learned about multidimensional arrays.
In this section, you're going to see how to pass multidimensional arrays to functions.

As you might have guessed, passing a multidimensional array to a function is similar
to passing a one-dimensional array to a function. You can either pass the unsized format
of a multidimensional array or a pointer that contains the start address of the
multidimensional array to a function. Listing 16.6 is an example of these two methods.

The following output is displayed on the screen after the executable
(16L06.exe) is executed:

OUTPUT

C:\app>16L06
The sum returned by DataAdd1(): 30
The sum returned by DataAdd2(): 30
C:\app>

ANALYSIS

At the beginning of the program in Listing 16.6, I declare two functions,
DataAdd1() and DataAdd2(), in lines 4 and 5. Note that the first argument to
DataAdd1() in line 4 is the unsized array of list. In fact,
list is a two-dimensional integer array declared in lines 9 and 10 inside the
main() function body. The other two arguments,
max1 and max2, are two dimension sizes of the list array.

As you can tell from the definition of
DataAdd1() in lines 22_31, each element of the
list array, expressed as list[i][j], is added and assigned to a local variable called
sum that is returned at the end of the DataAdd1() function in line 30. Here
i is from 0 to max1 - 1, and j is within the range of
0 to max2 - 1.

The DataAdd1() function is called in line 14, with the name of the
list array and the two dimension sizes, 2 and 5. The result returned by
DataAdd1() is printed out by the statement in lines 13 and 14. So you see, passing a multidimensional array to a function is
quite similar to passing a one-dimensional array to a function.

Another way to do the job is to pass a pointer that contains the start address of
a multidimensional array to a function. In this example, the
DataAdd2() function is declared in line 5 with a pointer expression,
int *list, as the function's first argument. The definition
of DataAdd2() is given in lines 33_42.

Note that in line 40, each element in the list array is fetched by moving the pointer to
point to the memory location of the element. That is, the dereferenced pointer
*(list + i*max2 + j) returns the value of an element that is located at row
i and column j, if you imagine that the two-dimensional array has both a horizontal and a vertical dimension.
Therefore, adding i*max2 to list calculates the address of row
i (that is, rows 0 through i-1 are skipped over); then adding
j calculates the address of element j (that is, column
j) in the current row (i). In this example, the range of the row is from
0 to 1 (that is, 2 rows total); the range of the column is from
0 to 4 (that is, 5 columns total). (See Figure 16.1.)

The result returned by the DataAdd2() function is displayed on the screen by the
statement declared in lines 16 and 17.

Figure 16.1.The two-dimensional coordinate shows the locations of
the elements in the list array.

In many cases, it's useful to declare an array of pointers and access the contents
pointed to by the array by dereferencing each pointer. For instance, the following
declaration declares an int array of pointers:

int *ptr_int[3];

In other words, the variable ptr_int is a three-element array of pointers to integers.
In addition, you can initialize the array of pointers. For example:

A piece of a poem written by Lord Byron is printed out after the executable
(16L07.exe) of the program in Listing 16.7 is created and executed:

OUTPUT

C:\app>16L07
There's music in the sighing of a reed;
There's music in the gushing of a rill;
There's music in all things if men had ears;
There earth is but an echo of the spheres.
There's music in the sighing of a reed;
There's music in the gushing of a rill;
There's music in all things if men had ears;
There earth is but an echo of the spheres.
C:\app>

ANALYSIS

Let's first have a look at the array of pointers,
str, which is declared and initialized in lines 9_13 inside the
main() function body of the program in Listing 16.7. As
you can see, str is a four-element array of pointers to a set of character strings. I have
adopted four sentences of a poem written by Lord Byron and used them as four character
strings in the program.

You can get access to a character string by using a corresponding pointer in the array.
In fact, there are two functions, StrPrint1() and
StrPrint2(), in Listing 16.7. Both of them can be called to gain access to the character strings. From the function declaration in line 4,
you can see that the StrPrint1() function is passed with a pointer of pointersthat is,
**str1, which is dereferenced inside the
StrPrint1() function to represent the four pointers that point to
the four character strings. The definition of
StrPrint1() is in lines 23_29.

The StrPrint2() function, on the other hand, only takes a pointer variable as its argument,
and prints out a character string referenced by the pointer. Lines 31_35 give the definition
of the StrPrint2() function.

Now back to the main() function. The
StrPrint1() function is called in line 16 with the
name of the array of pointers, str, as the argument.
StrPrint1() then displays the four sentences of Byron's poem on the screen. The
for loop in lines 17 and 18 does the same thing by
calling the StrPrint2() function four times. Each time, the start address of a sentence is passed
to StrPrint2(). Therefore, you see all the sentences of the poem printed on the screen twice.

Before you finish the course for this hour, there is one more interesting thing you need
to learn about: pointers to functions.

As with pointers to arrays, you can declare a pointer that is initialized with the left
value of a function. (The left value is the memory address at which the function is located.)
Then you can call the function via the pointer.

The program in Listing 16.8 is an example that declares a pointer to a function.

After the executable, 16L08.exe, of the program in Listing 16.8 is created and executed,
the following output is shown on the screen:

OUTPUT

C:\app>16L08
Pointing to a function.
Done!
C:\app>

ANALYSIS

As usual, a function declaration comes first in Listing 16.8. The
StrPrint() function is declared with the int data type specifier and an argument of a
char pointer in line 4.

The statement in line 9 gives the declaration of a pointer
(ptr) to the StrPrint() function (that is, int (*ptr)(char
*str);).

Note that the pointer, ptr, is specified with the
int data type and passed with a char pointer. In other words, the format of the pointer declaration in line 9 is quite similar to
the declaration of StrPrint() in line 4. Please remember that you have to put the
*ptr expression between a pair of parentheses (( and
)) so that the compiler won't confuse it with a
function name.

In line 11, the left value (that is, the address) of the
StrPrint() function is assigned to the ptr pointer. Then, the
(*ptr)(str) expression in line 12 calls the
StrPrint() function via the dereferenced pointer
ptr, and passes the address of the string declared in line 8 to
the function.

From the definition of the StrPrint() function in lines 18_22, you can tell that the
function prints out the content of a string whose address is passed to the function as the
argument. Then, 0 is returned at the end of the function.

In fact, the if statement in lines 12 and 13 checks the value returned by the
StrPrint() function. When the value is 0, the
printf() function in line 13 displays the string of
Done! on the screen.

The output of the program in Listing 16.8 shows that the
StrPrint() function has been invoked successfully by using a pointer that holds the address of the function.

A The beauty of using pointers is that you can move pointers around to get
access to valid data that is saved in those memory locations referenced by the
pointers. To do so, you can perform the pointer arithmetic to add (or subtract) an
integer to (or from) a pointer. For example, if a character pointer,
ptr_str, holds the start address of a character string, the ptr_str+1 expression means to move to the
next memory location that contains the second character in the string.

Q How does the compiler determine the scalar size of a pointer?

A The compiler determines the scalar
size of a pointer by its data type specified in the declaration. When an integer is added to or subtracted from a pointer,
the actual value the compiler uses is the multiplication of the integer and the size
of the pointer type. For instance, given an int pointer
ptr_int, the ptr_int + 1 expression is interpreted by the compiler as
ptr_int + 1 * sizeof(int). If the size of the int type is
2 bytes, then the ptr_int + 1 expression really means to move 2 bytes higher from
the memory location referenced by the ptr_int pointer.

Q How do you get access to an element in an array by using a pointer?

A For a one-dimensional array, you can assign the start address of an array to
a pointer of the same type, and then move the pointer to the memory location
that contains the value of an element in which you're interested. Then
you dereference the pointer to obtain the value of the element. For
multidimensional arrays, the method is similar, but you have to think about the other
dimensions at the same time. (See the example shown in Listing 16.6.)

Q Why do you need to use arrays of pointers?

A In many cases, it's helpful to use arrays of pointers. For instance, it's
convenient to use an array of pointers to point to a set of character strings so that you
can access any one of the strings referenced by a corresponding pointer in the array.

To help solidify your understanding of this hour's lesson, you are encouraged to
answer the quiz questions and finish the exercises provided in the Workshop before you move
to the next lesson. The answers and hints to the questions and exercises are given in
Appendix E, "Answers to Quiz Questions and Exercises."

Given a char pointer, ptr_ch, an
int pointer, ptr_int, and a float pointer, ptr_flt,
how many bytes will be added, respectively, in the following expressions on
your machine?

ptr_ch + 4

ptr_int + 2

ptr_flt + 1

ptr_ch + 12

ptr_int + 6

ptr_flt + 3

If the address held by an int pointer,
ptr1, is 0x100A, and the address held by another
int pointer, ptr2, is 0x1006, what will you get from the subtraction of
ptr1-ptr2?

Given that the size of the double data type is
8 bytes long, and the current address held by a
double pointer variable, ptr_db, is 0x0238, what are the addresses
held, respectively, by ptr_db-1 and ptr_db+5?

Given a character string, I like
C!, write a program to pass the string to a
function that displays the string on the screen.

Rewrite the program of exercise 1. This time, change the string of
I like C! to I love C! by moving a pointer that is initialized with the start address of the string
and updating the string with new characters. Then, pass the updated string to
the function to display the content of the string on the screen.

Given a two-dimensional character array,
str, that is initialized as

char str[2][15] = { "You know what,", "C is powerful." };

write a program to pass the start address of
str to a function that prints out the content of the character array.

Rewrite the program in Listing 16.7. This time, the array of pointers is
initialized with the following strings: