Professional Documents
Culture Documents
Pointers in The C Programming L - Ninnat Aupala-1
Pointers in The C Programming L - Ninnat Aupala-1
Pointers in The C Programming L - Ninnat Aupala-1
Programming Language
* & ->
Ninnat Aupala
Pointers in The C
Programming Language
By
Ninnat Aupala
1.1 Introduction 1
2.1 Introduction 20
3.1 Introduction 24
4.1 Introduction 74
5.1 Introduction 87
6.1 Introduction 92
Chapter 1
An introduction to pointers
1.1 Introduction
When a variable is defined in a C program, the compiler will find and allocate unused
locations in memory to that variable to use. Since the area of memory consist of a lot
of cells, each cell is identified by a unique number called its memory address (or, for
short, that address), to distinguish it from the other cells in memory. Every memory
cell has one byte in size. So to access the desired memory location, the address of
that location is needed.
Like any other data such as 47 (an integer value), 32.91 (a floating-point value), 'D'
(a character value) or etc., an address can be held in a variable called a pointer
variable. A pointer variable, often called shortly that a pointer, is a variable that can
store the memory address of a variable or function defined in a program. With a
pointer, we can refer to every variable or function created in a program.
data_type * variable_name
The declaration starts with a data type and follows by the * operator and then
a variable’s name. Some white spaces can also be used in the declarations. The
following declarations are all equivalent:
int *p;
int* p;
int * p;
int * p;
int *p;
2
int* p;
or no white spaces
int*p;
Note that the form of pointer declaration is the same as the form of ordinary variable
declaration, just add the * operator before a variable’s name.
And we can also use some keywords, shown in the table below, with pointers in the
declaration.
Data Type
Keyword
char int float Double
signed Y Y
unsigned Y Y
short Y
long Y Y Y
long long Y
3
auto Y Y Y Y
register Y Y Y Y
static Y Y Y Y
extern Y Y Y Y
const Y Y Y Y
Table 1-1
The letters 'Y' in the above table indicate what keywords can be used to which
data types. From the table 1-1, some keywords can be grouped together as shown in
the table 1-2 below.
Group Keyword
Table 1-2
4
int n;
int *p;
p = &n;
From the statements above, the expression p = &n assigns the address of the variable
n to the pointer p, and p is said to “point to” n. The address-of operator is only applied
5
int n;
int *p = &n;
char y, *z = &y;
Note that the data type of pointers and that of variables that they are pointing to must
be corresponding. From above, the int pointer p is pointing to the int variable n and
the char pointer z is pointing to the char variable y.
int x = 1, y;
int *p = &x;
y = *p;
To access the content of the variable which the pointer p points to, the * operator is
applied as shown in the last statement. Accessing what a pointer points to in this
way is called dereferencing the pointer because the pointer is considered to be
referencing the variable.
Since p points to the variable x, then *p can be used in any context where x could,
so the above expression y = *p is equivalent to an expression y = x. As a variable
itself, a pointer can be used without the * or & operator. For example, if z is another
pointer to an int, the expression z = p copies the contents of p into the pointer z, thus
making z point to whatever p pointed to.
6
An lvalue (left value) is an expression that can be on the left side of an assignment
operator and a data value can be assigned to it.
An rvalue (right value) is an expression that can be on the right side of an assignment
operator and a data value cannot be assigned to it.
The following table summarizes the use of the & and * operator.
Operator expression as
declaration
lvalue rvalue
* Y Y Y
& Y
(no operator) Y Y
Table 1-3
int x = 1, y, *p;
p = &y;
*p = 2;
x = *p;
7
In the expression *p = 2, *p serves as an lvalue, so the value 2 is put into the variable
that is being pointed to by the pointer p, the variable y. This expression is the same
as y = 2. For the next expression, x = *p, *p serve as an rvalue, so it gives the
contents of the variable that it is pointing to. Then, the expression x = *p puts the
contents of y, the value 2, into x. This expression is the same as x = y.
/* Program 1-1 */
#include <stdio.h>
void main(void)
{
int x = 1;
x occupies 4 bytes
The value of x = 1
When you run this program on your computer, you will have the different result in
that all addresses in this book are arbitrary to help us to have simple numbers that we
can refer to. From the program 1-1, we will represent the memory location of the
variable x by the diagram as shown below:
8
x variable name
1 data value
Note in the last printf( ) function, the expression *&x gives the content of x
because when the * and & operator are written consecutively, they cancel each
other. So *&x is the same as x.
/* Program 1-2 */
#include <stdio.h>
void main(void)
{
int *p, x = 3;
p = &x;
The value of x = 1
In the program 1-2, we have declared a pointer named p, and then we have assigned
the address of x to it. The memory diagram of two variables in this program are shown
below.
9
p x
100 1
200 100
Figure 1-2
The arrow from the pointer p to the variable x shows that p is pointing to x, thus the
value stored by p is the address of x. And note that although x contain several bytes
of memory, p will store only the address of the first byte of x.
p
100
200 100 101 102 103
x
1
100
Figure 1-3
/* Program 1-3 */
#include <stdio.h>
void main(void)
{
int x = 1, y = 2;
swap(&x, &y);
10
temp = *x;
*x = *y;
*y = temp;
}
x = 2, y = 1
In the calling function, main( ) function, the addresses of x and y are passed as
argument to the called function, swap( ) function. Note that the parameter x and y in
the swap function must be pointers to receive the addresses that are passed from the
main function. Because having the addresses of the variable in function main, the
swap function’s parameters can access and change variable in the function that called
it. Although the name of arguments and parameters are the same but they are not the
same variables since they are in the different functions. And we may or may not
declare the name of parameters in a function prototype whether or not. And in
function prototype, parameters’ name can be omitted but not in function definition.
/* Program 1-4 */
#include <stdio.h>
int *func(void);
void main(void)
{
int *x;
11
x = func();
printf("\n----------\n");
printf("In main");
printf("\n----------\n");
int *func(void)
{
static int y = 1;
printf("----------\n");
printf("In func");
printf("\n----------\n");
return &y;
}
----------
In func
----------
&y = 7000
y = 1
----------
In main
----------
&x = 1500
x = 7000
*x = 1
Note that in front of the function’s name must be preceded by the * operator in both
function prototype and function definition.
12
All statements from above are the declarations of a pointer to a pointer. Only one
thing added is another * operator.
/* Program 1-5 */
#include <stdio.h>
void main(void)
{
int **pp, *p, n;
n = 1;
p = &n;
pp = &p;
Suppose that the address of a pointer to a pointer pp is 1000, a pointer p is 2000 and
a variable n is 3000.
pp p n
2000 3000 1
Remember that use a pointer to a pointer when you want to hold the address of a
pointer.
The brackets after each variable’s name identify it as an array and the number within
the square brackets indicates the number of elements in the array. Let’s take a look
the way for using array of pointers in the example below.
/* Program 1-7 */
#include <stdio.h>
void main(void)
{
int q = 1, v = 2, u = 3;
int *x[3];
x[0] = &q;
x[1] = &v;
x[2] = &u;
Suppose that the address of x, q, v and u is 100, 500, 600 and 700 respectively.
Note that the form of using an array of pointers is similar to ordinary pointers. One
thing added is a subscript enclosed by the brackets.
15
(data_type *)pointer_name
This operator is a unary operator and associate from right to left. It has the same
precedence as other unary operators. In C, type conversion is called casting. Consider
the following code fragment:
int i, *pi;
char *pc;
pc = (char *)&i;
pi = (int *)pc;
pc = (char *)ppi;
Note that the way to convert the data type of pointers is similar to ordinary variables
just only one thing that is added is an asterisk.
16
void * pointer_name
Let’s learn how to declare and use a generic pointer in the program 1-8.
/* Program 1-8 */
#include <stdio.h>
void main(void)
{
void *v;
char c = 'Z';
v = &c;
char *p = v;
The address of
===============
&v: 1000
&c: 2000
&p: 3000
---------------
The content of
===============
v = 2000
c = z
p = 2000
---------------
&c : v : p = 2000
c : *(char *)v : *p = Z
From the program 1-8, there are several interesting points about generic pointer.
We can assign the address of any data types to a generic pointer without an
explicit casting.
We can assign the content of a generic pointer to any type of pointer without
casting.
/* Program 1-9 */
#include <stdio.h>
void main(void)
{
char **a;
int **b;
float **c;
double **d;
The size of
==============
char * : 4
int * : 4
float * : 4
double * : 4
char ** = 4
int ** = 4
float ** = 4
double ** = 4
void * : 4
19
Note that all pointers are the same size, regardless of what they point to. You may
receive the different output when you run this program since the size of pointers
depend on the machine that you use. In this book, we use 32-bit machine, so all types
of pointers have 4 byte-size.
20
Chapter 2
Dynamic memory allocation
2.1 Introduction
Since once an array has been created, its size is fixed and cannot be changed
during execution of a program. But there are some situations that we are unable to
know in advance that how large an array should be such as when we have to
create an array to receive a message which a user will enter through a keyboard. So
we have to guess the size of an array that we think that large enough to hold data
from a user. If the array's size is more than we need we waste space, but if it is
less than we need we can’t hold total data.
void *malloc(size_t s)
The malloc( ) function takes one parameter, which is the size of the requested area
of memory in bytes. It returns the address of the first byte of allocated memory area
if the allocation is successful. But if the memory allocation fails or the parameter
value is 0, it returns NULL. The allocated memory is contiguous and not cleared.
Note that the data type of a return value is a generic pointer since any kind of pointer
can be returned from the function which make it more flexible. And also note that
the data type of the parameter in this function is size_t. Remember that size_t is a
synonym for an unsigned integer type. The following code fragments shows the use
of this function:
21
long *p;
p = (long *)malloc(sizeof(long));
In fact, we can specify the number of bytes that we desire directly but since the size
of the data types vary from system to system, so we use the sizeof operator to get the
correct size and make the code more portable. For the generic pointer that malloc( )
returns, we actually will convert it whether or not but in this case we cast it for clarity.
We can also allocate memory dynamically by using function calloc( ). The function
prototype of this function is
The calloc( ) function takes two parameters, which is the number of items and the
size of each item in bytes. It returns the address of the first byte of allocated memory
area if the memory allocation is successful. But if the allocation fails or either
parameters or both parameters is 0, it returns NULL. The allocated memory is
contiguous and cleared (set all bytes to zero.)
The size of allocated memory results in the product of two function parameters
which is 5 x sizeof(int).
To change the size of memory location that was previously allocated by malloc( )or
calloc( ) we use realloc( ) function. The function prototype of this function is
The realloc( ) function takes two parameters which is the address of original memory
area and the new size that is required. The result that we can obtain from this function
is shown in the table below.
If the second parameter is 0, the allocated memory area is freed and the function
returns NULL.
If the new size is smaller, the function returns the original address.
If the new size is larger and the function can extend the original memory area, it
returns the original address.
If the new size is larger and the function cannot extend the original area of memory,
it will allocate the new one and copy the original content to it. The old memory area
is freed and the address of the new one is returned. But if the function cannot
allocate new memory area, it returns NULL and the original one is unchanged.
Table 2-1
The area of memory which we obtain from malloc( ), calloc( ) and realloc( ) come
from the heap segment and it is not disappeared when a function that it is resident
terminate. Thus, it is our responsibility to deallocate (or release or free) this memory
23
area to the system when we don’t need it by calling the free( ) function. The function
prototype of free( ) function is
char *p = malloc(70);
free(p);
p = NULL;
Remember that the allocated memory should not be accessed again after it has been
released but there is possibility that this situation will happen incidentally since its
address is still left in the pointer p. So we have to assign NULL to p in the code
above.
float f;
float *p = malloc(sizeof(float));
p = &f;
From the code above, note that the dynamically allocated memory's address has been
lost since we’ve reassigned the new address to the pointer p before we will free the
old one.
24
Chapter 3
Pointers and Arrays
3.1 Introduction
In C, pointer and array are closely related to each other because they can be used
interchangeably in almost context but not all. Several operations that can be done by
using arrays can also be done with pointers. Consider the following program.
/* Program 3-1 */
#include <stdio.h>
void main(void)
{
int z[3] = { 1, 2, 3 };
2 2 2 2
Firstly, consider the expression *(z+1) in the second printf( ) function. Actually, this
expression is equivalent to the expression z[1] in the first printf( ) function. Since
when a C program is compiled, the expression z[1] is translated into *(z+1) by a
compiler. That is, the name of an array is treated as the address of the first element
of the array (or base address) and its subscript as an offset. So we can use either of
these expressions to get the content of z[1]. Next, consider the expression *(1+z) in
the third printf( ) function and the expression 1[z] in the fourth printf( ) function.
Like the previous consideration, the expression *(1+z) is equivalent to the expression
1[z] because when the program is complied, 1[z] is translated into *(1+z). In addition,
from commutative property for addition, the order does not matter. Hence, *(1+z) is
equivalent to *(z+1). All in all the whole expressions in the program are the same.
So all printf( ) function print the same results, 2.
25
In addition to using array notation to refer to an array element, we can also refer to
an array element by using a pointer. Before we can refer to a desired element of an
array by a pointer, we have to assign the address of that array to a pointer. Look at
the following code fragment:
p = z; /* method 1 */
Another way to assign the address of an array to the pointer p is shown below.
p = &z[0]; /* method 2 */
Note that the address-of operator, &, is needed for the second method. The both ways
yield the same result. Now, the pointer p points to the array z (p contains the address
of 0th element). Then, any elements in the array z can be accessed through p. And
when a pointer contains the address of an array, it can refer to any array elements
with an array form too.
/* Program 3-2 */
#include <stdio.h>
void main(void)
{
int z[] = { 1, 2, 3, 4, 5 };
int *p = z;
1 1 1
2 2
26
2) The square brackets are applied with the pointer p to refer to the desired array
element. Then, there is no need to use the * operator because the expression p[0]
and p[1] will be translated into *(p+0) and *(p+1) in turn.
3) When we see a [0] to the right of a pointer, we can replace it with a * to the left of
the pointer.
When we want to pass an array to function, we have two optional forms of function
parameter definition as shown below.
/* Program 3-3 */
#include <stdio.h>
func1(z);
func2(&z[0]);
}
printf("\n");
27
printf("\n");
}
1 2 3 4 5
1 2 3 4 5
Both forms are the same. However, the first form may have a bit more advantage
because it will remind us that what is passed is the address of an array.
When we want to return an array from a function, we have just one way that is
returning its address. The form of function definition is shown below in a program
2-4.
/* Program 3-4 */
#include <stdio.h>
void *func(void)
{
static int a[5] = { 0, 1, 2, 3, 4 };
return a;
}
void main(void)
{
int *p;
p = func();
28
0 1 2 3 4
A two-dimensional array (2D array) is an array of arrays. That is, each element of an
array is another array. Consider the following array definition:
int z[3][3] =
{
{ 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 }
};
This is an array of 3 arrays of 3 ints. That is, z is an array with three elements. Each
of the three elements of z is an array of 3 ints.
an array
z
an array of 3 arrays
z[0] z[1] z[2]
Figure 3-1
29
We can also visualize this array as a table consisting of three rows and three columns.
The first subscript, next to the array name, is a row number and the second one is a
column number. All arrays, no matter how many dimensions they have, are stored
contiguously in memory, as shown in a figure below.
1 2 3 4 5 6 7 8 9
1 2 3
4 5 6
7 8 9
0th 2nd
1st
First column Third column
Second column
Figure 3-2
To hold the address of this array, we have to create a pointer to a 2D array as shown
below.
int(*p)[3];
p = z;
or
int(*p)[3] = z;
30
Keep in mind that the parentheses enclosing an asterisk and the pointer name are
important. If we don’t have the parentheses, p will become an array of pointers in
that the square brackets have higher precedence than the asterisk. The below table
lists the order of operator precedence.
+------------------------------+---------------+
| Operator | Associativity |
+------------------------------+---------------+
| | |
| ( ) [ ] . -> | left to right |
| | |
| * & ++ -- (data_type) sizeof | right to left |
| | |
| + - | left to right |
| | |
| = += -= | right to left |
| | |
+------------------------------+---------------+
| Note: |
| |
| - operators on the same line have the same |
| precedence |
| |
| - operators on the lower line have the lower |
| precedence |
| |
+----------------------------------------------+
Table 3-1
/* Program 3-5 */
#include <stdio.h>
func1(z);
func2(&z[0]);
}
printf("\n");
}
printf("\n");
}
void func2(int(*p)[3])
{
int(*p2)[3];
printf("In func2:\n");
printf("---------\n");
32
printf("\n");
}
}
In func1:
---------
1 2 3
4 5 6
7 8 9
In func2:
---------
1 2 3
4 5 6
7 8 9
As we can see from the program 3-5, there are two forms to declare function
parameters to receive the address of 2D arrays. In the first method, the leftmost
subscript can be omitted.
/* Program 3-6 */
#include <stdio.h>
int(*func(void))[3];
void main(void)
{
int(*p)[3] = func();
33
printf("\n");
}
}
int(*func(void))[3]
{
static int a[3][3] =
{
{ 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 }
};
return a;
}
1 2 3
4 5 6
7 8 9
Note that it is similar to the general form of declaring a pointer to 2D array just
replace a pointer name with a function name and add the parentheses at the end of
the function name.
34
int z[3][2][2] =
{
{
{ 0, 2 },
{ 4, 6 }
},
{
{ 1, 3 },
{ 5, 7 }
},
{
{ 11, 22 },
{ 33, 44 }
}
};
This is an array of 3 arrays of 2 arrays of 2 ints. That is, z is an array with three
elements. Each of the three elements is an array of 2 arrays. Each of the two elements
is an array of 2 ints.
35
an array
an array of 3 arrays
Figure 3-3
We can also visualize this array as a nested table (or a stack of table). Each table
consists of two rows and two columns. The first subscript specifies a desired table.
The second and the third subscript specify a row and a column respectively.
36
0 2 1 3 11 22
4 6 5 7 33 44
First table Second table Third table
11 22
1 333 44
0 52 7
4 6
Figure 3-4
37
/* Program 3-7 */
#include <stdio.h>
func1(&z[0]);
func2(z);
}
printf("\n");
}
printf("\n");
}
38
printf("\n");
}
void func2(int(*p)[2][2])
{
int(*p2)[2][2];
printf("In func2:\n");
printf("---------\n");
printf("\n");
}
printf("\n");
}
}
In func1:
---------
0 2
4 6
1 3
5 7
11 22
33 44
In func2:
---------
0 2
4 6
39
1 3
5 7
11 22
33 44
Note that the forms of declaring function parameter to receive the address of 3D
array is similar to 2D array just one thing that is increased is another subscript.
Note that it is similar to the form of 2D array just add another square bracket.
/* Program 3-8 */
#include <stdio.h>
int(*func(void))[2][2];
void main(void)
{
int(*p)[2][2] = func();
printf("\n");
}
printf("\n");
}
}
40
int(*func(void))[2][2]
{
static int a[][2][2] =
{
{ { 0, 2 }, { 4, 6 } },
{ { 1, 3 }, { 5, 7 } },
{ { 11, 22 }, { 33, 44 } }
};
return a;
}
0 2
4 6
1 3
5 7
11 22
33 44
41
Operation Result
Table 3-2
and
address = z + i
content = *( z + i )
42
p = &z[1];
p = &z[1]
= &(z[1])
= &(*(z + 1))
= &*(z + 1)
= z + 1
= 100 + (1 * sizeof(float))
= 100 + (1 * 4)
= 104
The address that we get is 104 not 101 since z is an array of floats, each element of
which consists of 4 bytes in size. So the offset has to be multiplied by the size of
element before it will be added to the base address. The expression z+1 gives the
address of the next element of the array z which is the address of the second element.
The same thing happens to subtraction when we subtract an integer from an address.
short z[5];
short *p = z + 3;
p = p – 1;
p = z + 3
= 100 + (3 * sizeof(short))
43
= 100 + ( 3 * 2 )
= 100 + 6
= 106
The expression z+3 gives the address of the next 3 element of the array z which is
the address of the fourth element. Since z now is an array of shorts, each element of
which consists of 2 bytes in size.
p = p – 1
= 106 - (1 * sizeof(short))
= 106 - ( 1 * 2 )
= 104
Note that the offset has to be multiplied by the size of element before it will be
subtracted from the address as addition. Now, the pointer p points to the second
element of the array z.
For subtracting two addresses from each other, the result of subtraction will be an
offset value which is an integer. Once again, assume that the address of z is the same
as before.
x = z + 4;
y = &z[1];
n = x - y;
n = x – y
= ( z + 4 ) - ( z + 1 )
= ( z + (4 * sizeof(int)) ) - (z + (1 * sizeof(int)) )
= ( 100 + ( 4 * 4 ) ) - ( 100 + ( 1 * 4 ) )
44
= ( 100 + 16 ) - ( 100 + 4 )
= 116 - 104
= 12
The value 12 is the number of bytes but we want an offset value, so we have to divide
this value with the data type.
n = 12 / sizeof(int)
= 12 / 4
= 3
n = x - y
= ( z + 4 ) - ( z + 1 )
= z + 4 - z – 1
= 3
Because x and y is pointing to the same array, x is essentially the same as z + 4 and
y is the same as z + 1.
45
and
+--------------------------+
| offset2 = row * COLSIZE |
| offset1 = column |
+--------------------------+
| row : row number |
| column : column number |
| COLSIZE : column size |
+--------------------------+
= *( a + ( i * COLSIZE ) )
= *( a + ( i * COLSIZE ) ) + j
= *( *( a + ( i * COLSIZE ) ) + j )
46
/* Program 3-9 */
#include <stdio.h>
void main(void)
{
int z[3][3] =
{
{ 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 }
};
int(*p)[3] = z;
printf("\n");
}
printf("\n");
printf("p = %d\n", p);
printf("p+1 = %d\n", p + 1);
printf("p+2 = %d\n\n", p + 2);
printf("\n");
printf("p[0] = %d p[0][0] = %d\n", p[0], p[0][0]);
printf("p[1] = %d p[1][0] = %d\n", p[1], p[1][0]);
printf("p[2] = %d p[2][0] = %d\n", p[2], p[2][0]);
printf("\n");
printf("p[0]+1 = %d p[0][1] = %d\n", p[0] + 1, p[0][1]);
printf("p[1]+1 = %d p[1][1] = %d\n", p[1] + 1, p[1][1]);
printf("p[2]+1 = %d p[2][1] = %d\n", p[2] + 1, p[2][1]);
printf("\n");
printf("p[0]+2 = %d p[0][2] = %d\n", p[0] + 2, p[0][2]);
printf("p[1]+2 = %d p[1][2] = %d\n", p[1] + 2, p[1][2]);
printf("p[2]+2 = %d p[2][2] = %d\n", p[2] + 2, p[2][2]);
}
47
p = 100
p+1 = 112
p+2 = 124
The detail for calculating the address of each element of the array z is explained
below.
z z+1 z+2
100 112 124
1 2 3 4 5 6 7 8 9
100 104 108 112 116 120 124 128 132
1 2 3
4 5 6
7 8 9
Figure 3-5
49
Let’s use what we’ve just learned to find the address of 2D array.
p
100
z z+1 z+2
z+1
z+2
Figure 3-6
Since p is a pointer to an array (an array of 3 ints), the thing that p is pointing to is
the entire subarray (or row) not single element in a row.
p + 1 = z + 1
= 100 + ( 1 * COLSIZE )
= 100 + ( 1 * ( 3 * sizeof(int)) )
= 100 + ( 1 * ( 3 * 4 ) )
50
= 100 + ( 1 * 12 )
= 100 + 12
p+1
112
z z+1 z+2
z+1
z+2
Figure 3-7
When p is added by 1, it will point to the next row of the array (or table). So the
address that we obtain is 112 not 104 because in each row consists of 3 elements (or
column) and each element in column is an int.
p + 2 = z + 2
= 100 + ( 2 * COLSIZE )
= 100 + ( 2 * ( 3 * sizeof(int) ) )
51
= 100 + ( 2 * ( 3 * 4 ) )
= 100 + ( 2 * 12 )
= 100 + 24
p+2
124
z z+1 z+2
z+1
z+2
Figure 3-8
/* the address of the first column in the first row of the table */
52
/* the address of the first column in the second row of the table */
/* the address of the first column in the third row of the table */
p p[1] p[2]
Figure 3-9
1 4 7
1 2 3 4 5 6 7 8 9
100 104 108 112 116 120 124 128 132
1 2 3
4 5 6
7 8 9
Figure 3-10
p[0]+1 = 100 + 1
= 100 + ( 1 * sizeof(int) )
= 100 + ( 1 * 4 )
= 100 + 4
= 104
/* the address of the second column in the first row of the table*/
54
p[1]+1 = 112 + 1
= 112 + ( 1 * sizeof(int) )
= 112 + ( 1 * 4 )
= 112 + 4
= 116
/* the address of the second column in the second row of the table */
p[2]+1 = 124 + 1
= 124 + ( 1 * sizeof(int) )
= 124 + ( 1 * 4 )
= 124 + 4
= 128
/* the address of the second column in the third row of the table */
55
Figure 3-11
Once p is dereferenced or subscripted with only one square brackets, it will decay to
a pointer to an int. After that, if we add 1 to it, it will point to the next single element
in subarray.
1 4 7
1 2 3 4 5 6 7 8 9
100 104 108 112 116 120 124 128 132
1 2 3
4 5 6
7 8 9
Figure 3-12
p[0]+2 = 100 + 2
= 100 + ( 2 * sizeof(int) )
= 100 + ( 2 * 4 )
= 100 + 8
= 108
/* the address of the third column in the first row of the table */
p[1]+2 = 112 + 2
= 112 + ( 2 * sizeof(int) )
= 112 + ( 2 * 4 )
= 112 + 8
57
= 120
/* the address of the third column in the second row of the table */
p[2]+2 = 124 + 2
= 124 + ( 2 * sizeof(int) )
= 124 + ( 2 * 4 )
= 124 + 8
= 132
/* the address of the third column in the third row of the table */
Figure 3-13
58
1 4 7
1 2 3 4 5 6 7 8 9
100 104 108 112 116 120 124 128 132
1 2 3
4 5 6
7 8 9
Figure 3-14
59
and
content = *(*(*(base address + offset3) + offset2) + offset1)
+-------------------------------------+
| offset3 = table * ROWSIZE * COLSIZE |
| offset2 = row * COLSIZE |
| offset1 = column |
+-------------------------------------+
| table : table number |
| row : row number |
| column : column number |
| ROWSIZE : row size |
| COLSIZE : column size |
+-------------------------------------+
For example, to find out the address of a[i]:
address = *( base address + offset3 )
= *( a + ( i * ROWSIZE * COLSIZE ) )
= *( *( a + ( i*ROWSIZE*COLSIZE ) ) + ( j*COLSIZE ) )
= *(*(a+(i*ROWSIZE*COLSIZE)) + (j*COLSIZE)) + k
= *(*(*(a+(i*ROWSIZE*COLSIZE))+(j*COLSIZE))+k)
60
#include <stdio.h>
void main(void)
{
short z[3][2][2] =
{
{ { 0, 2 }, { 4, 6 } },
{ { 1, 3 }, { 5, 7 } },
{ { 11, 22 }, { 33, 44 } }
};
short(*p)[2][2] = z;
printf("--------------------------------\n");
printf("\n------------------------------\n");
printf("\n------------------------------\n");
61
printf("\n------------------------------\n");
&z[1][0][0]:108 &z[1][0][1]:110
&z[1][0][1]:112 &z[1][1][1]:114
&z[2][0][0]:116 &z[2][0][1]:118
&z[2][1][0]:122 &z[2][1][1]:124
----------------------------------
p = 100
p+1 = 108
p+2 = 116
----------------------------------
p[0] = 100 p[0]+1 = 104
p[1] = 108 p[1]+1 = 112
p[2] = 116 p[2]+1 = 120
----------------------------------
p[0][0] = 100 p[0][0][0] = 0
p[0][1] = 104 p[0][1][0] = 4
p[1][0] = 108 p[1][0][0] = 1
p[1][1] = 112 p[1][1][0] = 5
p[2][0] = 116 p[2][0][0] = 11
p[2][1] = 120 p[2][1][0] = 33
----------------------------------
p[0][0]+1 = 102 p[0][0][1] = 2
p[0][1]+1 = 106 p[0][1][1] = 6
p[1][0]+1 = 110 p[1][0][1] = 3
p[1][1]+1 = 114 p[1][1][1] = 7
p[2][0]+1 = 118 p[2][0][1] = 22
p[2][1]+1 = 122 p[2][1][1] = 44
The detail for calculating the address of each element of the array z is explained
below.
&z[0][0][0]:100 &z[0][0][1]:102
&z[0][1][0]:104 &z[0][1][1]:106
&z[1][0][0]:108 &z[1][0][1]:110
&z[1][0][1]:112 &z[1][1][1]:114
63
&z[2][0][0]:116 &z[2][0][1]:118
&z[2][1][0]:120 &z[2][1][1]:122
11 22
1 333 44
0 52 7
4 6
Figure 3-15
64
Let’s use what we’ve just learned to find the address of 3D array.
100
a[2]
a[1]
a[0]
Figure 3-16
65
p+1 = z + 1
= 100 + ( 1 * ( 2 * 2 * sizeof(short) ) )
= 100 + ( 1 * ( 2 * 2 * 2 ) )
= 100 + ( 1 * 8 )
= 100 + 8
p+1
108
a[2]
a[1]
a[0]
Figure 3-17
66
p+2 = z + 2
= 100 + ( 2 * ( 2 * 2 * sizeof(short) ) )
= 100 + ( 2 * ( 2 * 2 * 2 ) )
= 100 + ( 2 * 8 )
= 100 + 16
p+2
116
a[2]
a[1]
a[0]
Figure 3-18
67
z[2]
z[1] z[2]+1
z[0]
z[1]+1
z[0]+1
Figure 3-19
After p is dereferenced or subscripted with one set of the square brackets, it will
decay into a pointer to a 1D array ( an array of 2 shorts ).
68
p[0]+1 = 100 + 1
= 100 + ( 1 * COLSIZE )
= 100 + ( 1 * ( 2 * sizeof(short) ) )
= 100 + ( 1 * ( 2 * 2 ) )
= 100 + ( 1 * 4 )
= 100 + 4
p[1]+1 = 108 + 1
= 108 + ( 1 * COLSIZE )
= 108 + ( 1 * ( 2 * sizeof(short) ) )
= 108 + ( 1 * ( 2 * 2 ) )
= 108 + ( 1 * 4 )
= 108 + 4
p[2]+1 = 116 + 1
= 116 + ( 1 * COLSIZE )
= 116 + ( 1 * ( 2 * sizeof(short) ) )
= 116 + ( 1 * ( 2 * 2 ) )
= 116 + ( 1 * 4 )
= 116 + 4
z[2]
z[1] z[2]+1
z[0]
z[1]+1
z[0]+1
Figure 3-20
Note that if the expression such as *p, p[0], p[1] or p[2] is added by 1, it will give
the address of the second row of the table.
/* the address of the first column in the first row of the first table */
/* the address of the first column in the second row of the first table */
/* the address of the first column in the first row of the second table */
/* the address of the first column in the second row of the second table */
70
/* the address of the first column in the first row of the third table */
/* the address of the first column in the second row of the third table */
After p is double dereferenced or subscripted with two sets of the square brackets, it
will decays into a pointer to a short.
0 2 4 6 1 3 5 7 11 22 33 44
100 102 104 106 108 110 112 114 116 118 120 122
11 22
1 333 44
0 52 7
4 6
Figure 3-21
/* the address of the second column in the first row of the first table */
/* the address of the second column in the second row of the first table */
/* the address of the second column in the first row of the second table */
/* the address of the second column in the second row of the second table */
72
/* the address of the second column in the first row of the third table */
/* the address of the second column in the second row of the third table */
Note that if the expression such as **p, p[0][1], p[1][1] or p[2][1] is added by 1, it
will give the address of the second column in second row of the table.
0 2 4 6 1 3 5 7 11 22 33 44
100 102 104 106 108 110 112 114 116 118 120 122
11 22
1 333 44
0 52 7
4 6
Figure 3-22
74
Chapter 4
Pointers and Strings
4.1 Introduction
A string is a group of characters terminated by a null character (or null byte). The null
character is represented as '\0', a backslash followed a by a zero.
/* Program 4-1 */
#include <stdio.h>
void main(void)
{
printf("'\\0' \n");
printf("\n\n");
printf("'0' \n");
'\0'
%c =
%d = 0
'0'
%c = 0
%d = 48
75
Note that to display a percent sign ( % ) and a backslash by the printf( ) function, we
have to double itself. But for the double quote ( " ), we have to precede it by a
backslash.
The above output shows us that the null character is the nonprinting character whose
value is 0. Although consisting of two characters, ‘\’ and ‘0’, the null character is just
regarded as one character because it is an escape sequence like ‘\n’, ‘\t’ or etc.
Since C has no particular data type for storing strings, it uses an array of chars to
store a string instead. Consider the following code fragment:
From the statements above, all in all the second one is more simple and preferred. In
the first statement, the null character must be included at the end but not in the second
because it is automatically done when a group of characters is surrounded by the
double quotation marks (double quotes). And also remember that the double
quotation marks are not part of the string. They are used to inform that they enclose
a string, just as the single quotation marks (single quotes) identify a character. And
we don’t have to enclose each character in the single quotes when it is part of a string
(enclosed in double quotes).
Both methods as shown above are defining and initializing an array at the same time.
But if we don’t initialize an array at the same time as it is defined we must assign
an individual character in a string at a time as shown below.
76
char s[5];
s[0] = 'T';
s[1] = 'E';
s[2] = 'X';
s[3] = 'T';
s[4] = '\0';
s2 = "TEXT"; /* NO */
But initializing string character by character is a hard and boring work. The better
way is to apply strcpy( ) function as shown in the program 3-2 below.
/* Program 4-2 */
#include <stdio.h>
#include <string.h>
void main(void)
{
char s[5];
strcpy(s, "text");
printf("\n\n");
printf("\n\n");
printf("\n\n");
printf("%s", s);
77
TEXT
TEXT
Don’t forget to include string header file when strcpy( ) function or other string
functions are needed. And don’t forget to add another byte for an array to store a null
character. In the program, the strlen( ) function can be used to find the length of a
string but it does not count the null character at the end of the string. On the other
hand, the sizeof operator returns a number that one larger than the number returned
from strlen( ) because it also counts the invisible null character used to end the string.
To display a string on screen, we use two method in this program. The first method
is using a for loop and function printf( ) with %c format specification to display each
character in a string. The second one is using the %s format specification to display
all characters in the string in one time. Note that the second method is easier and
preferred.
Actually, any characters within strings can be letters, numbers, symbols (such as
escape sequences, double quotes, semicolon, etc.)
/* Program 4-3 */
#include <stdio.h>
void main(void)
{
char s1[] = "%AB!_-#?&\t";
char s2[] = "b9c: '(}]*\\";
char s3[] = "\n\"17Fe ;><";
printf("%s", s1);
printf("%s", s2);
printf("%s", s3);
}
78
Note that when we want to keep a backslash and a double quote in a string, we must
precede them by a backslash.
In fact, we have been always using strings so far such as in using it as an argument
in function printf( ). Consider the form of the function prototype of printf( ).
Note that the first argument of the function accept memory address. So we can call
the printf( ) function like this:
printf("Hello, world");
The string "Hello, world" can be passed as an argument to the function printf( )
because it evaluates to the address of the first character in the string. So we can use a
char pointer to store its address. For example
Let’s find out the different between the following string declarations
char c = 'Z';
char *p = "AAA";
p = "CCC"; /* OK */
*p = 'L'; /* NO - can’t write */
c = *p; /* OK - can read */
z = "CCC"; /* NO */
*z = 'L'; /* OK - can read */
c = *z; /* OK - can write */
strcpy(p, "TTT"); /* NO */
strcpy(z, "TTT"); /* OK */
p = z /* OK */
*p = 'N' /* OK */
Note that the quoted string "CCC" can be assigned to p because p is a pointer but
we cannot modify this string since it is stored in read-only memory. In the other hand,
we cannot assign "CCC" to z since z is an array of chars so it cannot hold an address
but z can modify its string as shown in the expression *z = 'L', which this expression
is equivalent to z[0] = 'L', because z itself has its memory location for storing string.
Moreover, p can also point to z and then, we can modify the string in z through p as
shown in the expression *p = 'N' which is equivalent to z[0] = 'N'.
The keyword const can be applied with a pointer as we have seen in the previous
topic in the function prototype of printf( ) function. When this keyword is used with
a pointer, its position affect what a pointer can do. Consider the following code
fragments:
/* 1 */
char *p;
p = &c1; /* OK */
*p = 'T'; /* OK */
c2 = *p; /* OK */
p = &cc; /* NO */
pc = &c1; /* OK */
*pc = 'W'; /* NO */
c2 = *pc; /* OK */
pc = &c2; /* OK */
pc = &cc; /* OK */
*pc = 'L'; /* NO */
c1 = *pc; /* OK */
c1 = 'A'; /* OK */
c2 = 'B'; /* OK */
cc = 'U'; /* NO */
From the above code fragment, pc is a pointer to a const char which mean it cannot
modify the char variable that it’s pointing to. That is, we cannot dereference pc to
change what it points to as shown in the expression *pc = 'W' and *pc = 'L'. In the
other word, we cannot dereference pc to write but we can dereference it to read as
shown in the expression c2 = *pc and c1 = *pc. Anyway, the pointer pc itself is not
constant, so we can change it to point to other char variables as in the expression
pc = &cc. In addition, note that pc can hold the address of c1, c2 and cc but p cannot
store cc’s address.
/* 2 */
*p = 'T'; /* OK */
c2 = *p; /* OK */
p = &c2; /* NO */
p is a const pointer to an int which mean after we have created this pointer, it cannot
be changed to point to any other char variables. So we have to initialize it at the same
time that we declare it. We can dereference this pointer to read or write.
81
/* 3 */
*p1 = 'T'; /* NO */
p1 = &c2; /* NO */
p1 = &cc1; /* NO */
p1 = &cc2; /* NO */
c2 = *p1; /* OK */
*p2 = 'T'; /* NO */
p2 = &c1; /* NO */
p2 = &c2; /* NO */
p2 = &cc2; /* NO */
c2 = *p; /* OK */
p1 and p2 is a const pointer to a const int which mean they cannot be changed to
point to any other char variables. We have to initialize them at the same time that we
declare them. We can only dereference these pointers to read. The below table
summarizes all things that are discussed in this section.
82
can store
can modify can can the address of
declaration read write
pointer variable
char const char
pointed
to
const char *p Y Y Y Y
char * const p Y Y Y Y
The order of a data type and keyword isn’t important. So the following declarations
are equivalent:
Table 3-1
83
/* program 4-4 */
#include <stdio.h>
void main(void)
{
char msg[] = "TEST";
return length;
}
TEST: 4
The function len( ) in this program emulate the function strlen( ) of C standard
library.
84
/* program 4-5 */
#include <stdio.h>
#include <malloc.h>
void main(void)
{
char msg1[] = "Hello, ";
char msg2[] = "world";
printf("%s", message);
free(message);
}
return length;
}
*s = '\0';
return(s - n);
}
Hello, world
Note that we can also hold strings in the heap area by using malloc( ) function.
86
When we want to hold several strings, we can create an array of pointers to keep
them.
char *p[] =
{
"Sophia",
"Peter",
"Bob",
"Alexandra"
};
p[0] 100 S o p h y \0
p[1] 106 P e t e \0
p[2] 111 B o b \0
p[3] 115 A l e x a n d r a \0
Figure 4-1
87
Chapter 5
Pointers and Structures
5.1 Introduction
Like the way we can have any other kinds of pointer variables, C also provide a kind
of pointer that can point to a structure variable. Such a pointer is called a structure
pointer or a pointer to a structure.
/* 1 */
struct book
{
char title[10];
float price;
};
/* 2 */
struct book
{
char title[10];
float price;
}*p;
There are two ways to access any members in a structure with a structure pointer.
(*pointer_name).member_name /* 1 */
pointer_name->member_name /* 2 */
In the first method, the parentheses are necessary because the precedence of the .
operator is higher than the * operator. For the second method, the -> operator, which
is the minus sign ( - ) followed by greater than operator ( > ), is a shorthand
of the first method. When these operators are applied with structures, we can call the
. operator that structure member-of operator and the -> operator that structure
member-access operator. Both . and -> associate from left to right.
/* Program 5-1 */
#include <stdio.h>
void main(void)
{
struct book
{
char *title;
float price;
};
p = &comic;
p->price = 14.99;
printf("%s\n", p->title);
The size of p: 4
To obtain the address of the structure variable comic, we have to place the
address-of operator, &, in front of it. So, from the expression p = &comic, the
address of comic is assigned to the structure pointer p. And also note that the size of
p is 4 bytes like all other kinds of pointers.
The form of declaring structure pointers as parameters of functions are the same as
they are declared as variables in functions.
/* Program 5-2 */
#include <stdio.h>
struct book
{
char *title;
float price;
};
void main(void)
{
struct book comic;
comic.price = 14.99;
display(&comic);
}
90
/* Program 5-3 */
#include <stdio.h>
struct book
{
char *title;
float price;
};
void main(void)
{
struct book *p = func();
printf("%s\n", p->title);
comic.price = 14.99;
91
return &comic;
}
To declare an array of structure pointers, just put the square brackets and a subscript
after a structure pointer’s name.
/* 1 */
struct book
{
char title[10];
float price;
};
/* 2 */
struct book
{
char title[10];
float price;
}*p[5];
92
Chapter 6
Pointers and Functions
6.1 Introduction
data_type (*pointer_name)(parameter_list)
The parentheses around a pointer’s name are necessary without them it would be a
function declaration since the * operator has the lower precedence than the ( )
operator. The data type of a return’s value and parameters of a function pointer must
correspond with a function that it point to. Let's look at the program 6-1 below to see
how to declare and use this kind of pointer.
/* Program 6-1 */
#include <stdio.h>
void func1(void);
void func2(void);
void main(void)
{
/* declaring function pointers */
void(*p1)(void);
void(*p2)(void);
93
p2();
(*p2)();
}
void func1(void)
{
printf("TEST1:");
}
void func2(void)
{
printf("test2:");
}
TEST1:TEST1:test2:test2:
Note that there are two methods to obtain the address of a function. The function’s
address can be obtained by using a function’s name without the parentheses. There
can be the address-of operator in front of a function pointer’s name whether or not.
And there are also two methods to call a function through a function pointer. We are
well familiar with the first method because it has the same form as a function call.
For the second method, there may be an advantage is it remind us that a function is
called through a pointer and the parentheses are required here too.
94
The form of declaring function pointers as parameters of functions are the same as
they are declared as variables in functions.
/* Program 6-2 */
#include <stdio.h>
void display(int(*)());
void main(void)
{
display(add);
}
Enter num1: 1
Enter num2: 2
3
95
data_type (*function_name(parameter_list))(parameter_list)
Note that a function’s name and parameters will be inside the first parentheses.
/* Program 6-3 */
#include <stdio.h>
int(*compute(void))();
void main(void)
{
int(*p)(int, int) = compute();
int(*compute(void))(int, int)
{
printf("Select operation\n");
printf("================\n");
printf(" + : Addition\n");
printf(" - : Subtraction\n\n");
printf("Enter character: ");
char c = getchar();
switch (c)
{
case '+': return add;
case '-': return sub;
}
}
Select operation
================
+ : Addition
- : Subtraction
Enter character: -
Enter number1: 1
Enter number2: 2
-1
Of course, we can create an array of function pointers like any other pointers. The
general form of an array of function pointers is
data_type (*pointer_name[index])(parameter_list)
To declare an array of function pointers, the square brackets and a subscript are put
after the name of function pointer.
97
/* Program 6-4 */
#include <stdio.h>
void main(void)
{
int(*p[3])(int, int) = { add, sub, mul };
printf("Select operation\n");
printf("==================\n");
printf("0 : Addition\n");
printf("1 : Subtraction\n");
printf("2 : Multiplication\n\n");
printf("choose: ");
scanf("%d", &n);
Select operation
==================
0 : Addition
1 : Subtraction
2 : Multiplication
choose: 2
Enter number1: 2
Enter number2: 2
Now, it’s time to learn how to interpret complex declarations. Let’s take a look at
the process for interpreting complex declaration.
Look to the right. If the square brackets are found, continue reading to the
step 2 right until there are no the square brackets left. Or if the opening parenthesis
is found, read up to the nearest closing parenthesis.
step 3 Look to the left. If the opening parenthesis is found, read up to the balancing
parenthesis and go back to step 2
step 4 If const or * is found, keep reading to the left, until it's not one of these and
go back to step 3.
3 N -
unsigned char *
4 Y pointers to
3,4 N -
unsigned char
5 Y an unsigned char
Note that the part of the declaration that is printed in bold is what we are dealing
with in each step.
100
2. float (*p)[2][4];
float (*p)[2][4] 1 Y p is
4 Y a pointer to
3 N -
float ( )[2][4]
4 Y
float 3,4 N -
5 Y floats
float ( * p )[2][4];
5 3 4 1 2 2
101
3. double(*p(int))[4][4];
double (*p(int))[4][4] 1 Y p is
3 N -
double (*)[4][4]
double ( )[4][4] 3 Y
3,4 N -
double
5 Y doubles
4. int(*p(void))(short,long);
int (*p(void))(short,long) 1 Y p is
int (*)(short,long) 3 N -
4 Y that return a
pointer to
int ( )(short,long) 3 Y
3,4 N -
int
3 N -
const char * const *(*)( )
4 Y pointers to
3 N -