54 Intermediate C Programming
Frame Symbol Address Value
sumarr
ind 110 garbage
len 107 5
intarr 106 100
return location 105 line 18
f
arr[4] 104 9
arr[3] 103 3
arr[2] 102 2
arr[1] 101 7
arr[0] 100 4
What is the difference between the following two statements?
a ++; // assume a is an integer1
// same as a = a + 1;2
intarr [ ind ] ++; // assume intarr [ ind ] is an integer3
// same as intarr [ ind ] = intarr [ ind ] + 14
The first line executes the following steps:
1. Reads a’s value.
2. Increments the value by one.
3. Writes the incremented value back to a.
The second statement does something similar:
1. Reads intarr[ind]’s value.
2. Increments the value by one.
3. Writes the incremented value back to intarr[ind].
Because intarr is the address of the array’s first element, the function incrarr can
read and modify the array’s elements even though the array is stored in a different frame.
This is the call stack after incrarr has finished, and its frame has been popped.
Frame Symbol Address Value
f
arr[4] 104 10
arr[3] 103 4
arr[2] 102 3
arr[1] 101 6
arr[0] 100 5
4.7 Type Rules
Here are some rules about types:
If var’s type is t, then & var’s type is t *.
If ptr’s type is t *, then * ptr’s type is t.
If arr is an array of type t, then each element stores a value of type t. Thus, the type
of an element (such as arr[1]) is t. Please notice the presence of an index.
If arr is an array of type t, then arr’s (without any index) type is t * because arr
is equivalent to & arr[0].
An array’s name is always a pointer. If arr is an array of type t and t * ptr
is a pointer of type t, then ptr = arr is a valid assignment. It is equivalent to
ptr = & arr[0], or assigning the address of the first element to ptr.
Pointers 55
Pointers are not necessarily arrays. For example, t * ptr creates a pointer of type t
and it is not related to any array. Hence, arr = ptr can be a dangerous assignment
because operations like arr[1] may read from (or write to) an invalid address.
4.8 Pointer Arithmetic
Pointers can be used to iterate through (visit) the elements of an array. This is called
pointer arithmetic. Consider the following example.
// ar i thmetic1 . c1
#in clude < stdio .h >2
#in clude < stdlib .h >3
int main ( i n t argc ,char * * argv )4
{5
int arr1 [] = {7 , 2, 5, 3 , 1 , 6, -8 , 16 , 4};6
char arr2 [] = { m , q , k , z , % , > };7
double arr3 [] = {3.14 , -2.718 , 6.626 , 0 . 5 2 9 } ;8
int len1 = s i z e o f ( arr1 ) / s i z e o f ( i n t ) ;9
int len2 = s i z e o f ( arr2 ) / s i z e o f ( char) ;10
int len3 = s i z e o f ( arr3 ) / s i z e o f ( double );11
printf (" lengths = %d , %d, %d n " , len1 , len2 , len3 );12
int * iptr = arr1 ;13
char * cptr = arr2 ;14
double * dptr = arr3 ;15
printf (" values = %d , %c , %f n" , * iptr , * cptr , * dptr );16
iptr ++;17
cptr ++;18
dptr ++;19
printf (" values = %d , %c , %f n" , * iptr , * cptr , * dptr );20
iptr ++;21
cptr ++;22
dptr ++;23
printf (" values = %d , %c , %f n" , * iptr , * cptr , * dptr );24
iptr ++;25
cptr ++;26
dptr ++;27
printf (" values = %d , %c , %f n" , * iptr , * cptr , * dptr );28
return EXIT _SUCCES S ;29
}30
This is the output of this program:
lengths = 9, 6, 4
values = 7, m, 3.140000
values = 2, q, -2.718000
values = 5, k, 6.626000
values = 3, z, 0.529000
Lines 6 to 8 create three arrays, one of integers, one of characters, and one of double-
precision floating point numbers. In a C program, you can create a constant array without
56 Intermediate C Programming
giving the size, by putting nothing between [ and ]. The compiler will automatically cal-
culate the array’s size. Lines 9 to 11 calculate the lengths of the three arrays. So far we
use the same size for different types (int, char, double) but different types actually take
up different amounts of memory, and therefore have different sizes. Thus, these three lines
divide the array sizes by the types’ sizes in order to get the numbers of the elements. Line
12 prints the lengths. As you can see, the program prints the correct lengths. This method
for calculating an array’s size is valid only for constant arrays. If an array is created using
malloc, this method will not work. A later chapter will explain malloc.
Lines 13 to 15 assign the addresses of the first element in each array to the pointers.
These three lines are equivalent to
int * iptr = & arr1 [0];1
char * cptr = & arr2 [0];2
double * dptr = & arr3 [0];3
Do not mix the pointer types. For example, the following statements are wrong:
int * iptr = arr2 ;1
int * iptr = arr1 [1];2
The first is wrong because arr2 is an array of char. The second is wrong because arr1[1]
is an int, and is not an address.
Line 16 prints the values stored at the corresponding addresses. The printed values are
the first elements. Lines 17 to 19 are called pointer arithmetic. Each pointer is advanced by
one. This means specifically, that each pointer now points to the next element of the array.
Line 20 prints the values stored at the corresponding addresses. The printed values are the
second elements. Lines 21 to 23 make each pointer point to the next element. Line 24 prints
the values stored at the corresponding addresses. The printed values are the third elements.
Even though different types have different sizes in memory, the compiler will automatically
move the pointers to correctly point to the next elements.
The sizes of types are not fixed by the C language, and can vary depending on the
computer, operating system, and specific compiler options chosen to compile the code. The
following program prints the sizes of various types:
// ar i thmetic2 . c1
#in clude < stdio .h >2
#in clude < stdlib .h >3
int main ( i n t argc ,char * * argv )4
{5
int arr1 [] = {7 , 2, 5, 3 , 1 , 6, -8 , 16 , 4};6
char arr2 [] = { m , q , k , z , % , > };7
double arr3 [] = {3.14 , -2.718 , 6.626 , 0 . 5 2 9 } ;8
long i nt addr10 = ( long i nt ) (& arr1 [0]) ;9
long i nt addr11 = ( long i nt ) (& arr1 [1]) ;10
long i nt addr12 = ( long i nt ) (& arr1 [2]) ;11
printf (" %ld , %ld , %ld n" , addr12 , addr11 , addr10 ) ;12
printf (" %ld , %ld n" , addr12 - addr11 , addr11 - addr10 );13
long i nt addr20 = ( long i nt ) (& arr2 [0]) ;14
long i nt addr21 = ( long i nt ) (& arr2 [1]) ;15
long i nt addr22 = ( long i nt ) (& arr2 [2]) ;16
printf (" %ld , %ld , %ld n" , addr22 , addr21 , addr20 ) ;17
printf (" %ld , %ld n" , addr22 - addr21 , addr21 - addr20 );18
long i nt addr30 = ( long i nt ) (& arr3 [0]) ;19
long i nt addr31 = ( long i nt ) (& arr3 [1]) ;20
Pointers 57
long i nt addr32 = ( long i nt ) (& arr3 [2]) ;21
printf (" %ld , %ld , %ld n" , addr32 , addr31 , addr30 ) ;22
printf (" %ld , %ld n" , addr32 - addr31 , addr31 - addr30 );23
return EXIT _SUCCES S ;24
}25
The output of the program is:
140735471859144, 140735471859140, 140735471859136
4, 4
140735471859186, 140735471859185, 140735471859184
1, 1
140735471859120, 140735471859112, 140735471859104
8, 8
We have already discussed lines 6 to 8, but what do lines 9 to 11 do? At the right side
of the assignment, & arr1[0] gets the address of the first element of arr1. This address
is assigned to addr10. Because this code is compiled on a 64-bit computer, the memory
addresses use 64 bits, and require the long int type to store the addresses. We need to use
some special syntax (called a type cast) to tell the compiler to store the memory address
inside an integer. This is why we have (long int) after =. In general, storing memory
addresses in integers is a very bad idea, because it can lead to subtle problems when the
code is compiled under different circumstances. Using (long int) is telling the compiler “I
know this is wrong, but trust me, I want to do it.” The purpose of this program is to show
you that the sizes of different types can be different.
Lines 10 and 11 get the addresses of the second, and the third elements of the array. Line
12 prints the values of these long integers. In printf, %ld is used to print a longer integer.
The value changes if you execute the program again. However, line 13 always prints 4, 4
meaning that the addresses of two adjacent elements differ by 4. This means each integer
uses 4 bytes of memory. Line 17 prints some addresses and they change when the program
is executed again. Line 18 always prints 1, 1 meaning that the addresses of two adjacent
elements differ by 1. Thus, each character needs 1 byte of memory. Line 23 always prints
8, 8 meaning that the addresses of two adjacent elements differ by 8. Thus, each double
needs 8 bytes of memory.
The next example combines these two programs.
// ar i thmetic3 . c1
#in clude < stdio .h >2
#in clude < stdlib .h >3
int main ( i n t argc ,char * * argv )4
{5
int arr1 [] = {7 , 2, 5, 3 , 1 , 6, -8 , 16 , 4};6
char arr2 [] = { m , q , k , z , % , > };7
double arr3 [] = {3.14 , -2.718 , 6.626 , 0 . 5 2 9 } ;8
int * iptr = & arr1 [3];9
printf (" %d n" , * iptr );10
long i nt addr13 = ( long i nt ) iptr ;11
iptr --;12
printf (" %d n" , * iptr );13
long i nt addr12 = ( long i nt ) iptr ;14
printf (" addr13 - addr12 = % ld n " , addr13 - addr12 );15
printf (" = = === ==== === = === ==== === = === ==== === === n ");16
58 Intermediate C Programming
17
char * cptr = & arr2 [1];18
printf (" %c n" , * cptr );19
long i nt addr21 = ( long i nt ) cptr ;20
cptr ++;21
printf (" %c n" , * cptr );22
long i nt addr22 = ( long i nt ) cptr ;23
printf (" addr22 - addr21 = % ld n " , addr22 - addr21 );24
printf (" = = === ==== === = === ==== === = === ==== === === n ");25
26
double * dptr = & arr3 [2];27
printf (" %f n" , * dptr );28
long i nt addr32 = ( long i nt ) dptr ;29
dptr --;30
printf (" %f n" , * dptr );31
long i nt addr31 = ( long i nt ) dptr ;32
printf (" addr32 - addr31 = % ld n " , addr32 - addr31 );33
return EXIT _SUCCES S ;34
}35
This is the output of the program:
3
5
addr13 - addr12 = 4
=====================================
q
k
addr22 - addr21 = 1
=====================================
6.626000
-2.718000
addr32 - addr31 = 8
Line 9 assigns the address of arr1[3] to iptr and line 10 prints the value stored at that
address. As you can see in this example, iptr does not have to start from the first element
of the array. Line 11 stores iptr’s value in addr13. Please remember that iptr’s value is
an address. Line 12 decrements iptr’s value and line 13 prints the value at address. The
value is 5, the same as arr1[2]. Line 14 stores iptr’s value in addr12. Line 15 shows the
differences of the two addresses stored in addr13 and addr12 and the difference is 4, not 1.
What does this mean? Even though line 12 decrements iptr by one, the compiler ac-
tually decreases iptr’s value by 4 because the size of an integer is 4. In other words, the
specific change in iptr’s value depends on the size of the type being pointed to. The outputs
for the other two arrays further illustrate this point. Line 24 prints 1 and line 33 prints 8
because of the sizes of the types being pointed to. This explains why mixing types can be
problematic. For example,
int * iptr = arr2 ; // arr2 is a char array1
int * iptr = arr3 ; // arr3 is a double array2
Programs have odd behavior when the types are mixed like this.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset