Programmer-Defined Data Types 259
#in clude < stdio .h >2
#in clude < stdlib .h >3
#in clude < string .h >4
#in clude " vector .h "5
Vector * V e ctor_ const r uct ( i n t a , i n t b , int c)6
// notice *7
{8
Vector * v ;9
v = malloc ( s i z e o f ( Vector ) );10
i f (v == NULL ) // allocat i on fail11
{12
printf (" malloc fail n ");13
return NULL ;14
}15
v -> x = a ;16
v -> y = b ;17
v -> z = c ;18
return v ;19
}20
void Vect o r_des truct ( Vector * v )21
{22
free ( v);23
}24
void Vector _print ( Vector * v )25
{26
printf (" The vector is (% d, %d , % d) . n ",27
v -> x , v -> y , v -> z );28
}29
int main ( i n t argc , char * argv [])30
{31
Vector * v1;32
v1 = V ector _ const ruct (3 , 6, -2);33
i f ( v1 == NULL )34
{35
return EXIT _FAILUR E ;36
}37
Vecto r_print ( v1 );38
Vect or_des truct ( v1 );39
return EXIT _SUCCES S ;40
}41
The program declares a Vector pointer at line 35. It will be given an address in heap
memory returned by Vector construct. Before calling Vector construct, the call stack
only has a frame for the main function:
Frame Symbol Address Value
main v1 100 garbage
Calling Vector construct pushes a frame on to the stack:
260 Intermediate C Programming
Frame Symbol Address Value
Vector construct
v 124 garbage
c 120 2
b 116 6
a 112 3
value address 108 100
return location 104 line 37
main v1 100 garbage
Calling malloc allocates a piece of memory that is in the heap. The size is sufficient to
accommodate three integers. Suppose malloc returns 60000. Then the call stack becomes:
Frame Symbol Address Value
Vector construct
v 124 60000
c 120 2
b 116 6
a 112 3
value address 108 100
return location 104 line 37
main v1 100 garbage
The heap memory is shown below:
Address Value
60008 garbage
60004 garbage
60000 garbage
The pointer’s value takes on the address returned by calling malloc. Since it is a pointer,
the program uses -> to access the attributes. The statement,
v -> x = a ;16
v -> y = b ;17
v -> z = c ;18
modifies the values at addresses 60000, 60004, and 60008 to 3, 6, and 2 respectively. The
heap memory is changed to:
Address Value
60008 garbage 2
60004 garbage 6
60000 garbage 3
When Vector construct returns, v’s value is written to the return address 100. There-
fore, the call stack becomes:
Frame Symbol Address Value
main v1 100 60000
Note that, as always, the memory allocated on heap must be released by calling free.
This is the purpose of the destructor Vector destruct.
Programmer-Defined Data Types 261
16.4 Constructors and Destructors
Sometimes objects need to contain pointers that manage dynamically allocated memory.
Consider the following example:
// person . h1
#i f n d e f PERSON_H2
#d ef in e PERSON_H3
typedef s t ru ct4
{5
int year ;6
int month ;7
int date ;8
char * name ;9
} Person ;10
Person * P e rson_ const r uct ( i n t y , i n t m , int d , char * n );11
void Pers o n_des truct ( Person * p );12
void Person _print ( Person * p );13
#e ndif14
Each Person object has four attributes, three for the date of birth and one for the name.
The name is a pointer because the length of a person’s name is unknown. If the name is
created with a fixed length, this attribute must use the longest possible name and waste
memory when a name is shorter. It is more efficient allocating the length of the name as
needed. This is the constructor:
#in clude " person .h "1
#in clude < stdio .h >2
#in clude < string .h >3
#in clude < stdlib .h >4
Person * P e rson_ const r uct ( i n t y , i n t m , int d , char * n )5
{6
Person * p = NULL ;7
p = malloc ( s i z e o f ( Person ) );8
i f (p == NULL ) // malloc fail9
{10
return NULL ;11
}12
p -> year = y ;13
p -> month = m;14
p -> date = d ;15
p -> name = malloc ( s i z e o f ( char) * ( strlen ( n) + 1) );16
// + 1 for the ending characte r 0 17
i f (( p -> name ) == NULL ) // malloc fail18
{19
free ( p);20
return NULL ;21
}22
strcpy (p -> name , n) ;23
return p ;24
}25
262 Intermediate C Programming
Notice how the constructor initializes the attributes in the same order as they are de-
clared in the header file. This is a good programming habit. For the sake of clarity, make
things as consistent as possible. This habit prevents accidentally forgetting to initialize an
attribute. Below is the destructor:
#in clude " person .h "1
#in clude < stdlib .h >2
void Pers o n_des truct ( Person * p )3
{4
// p -> name must be freed before p is freed5
free ( p -> name ) ;6
free ( p);7
}8
Note that the destructor releases memory in the reverse order that the construc-
tor allocates memory. This is a general rule. If the destructor free (p) precedes
free (p -> name), then the program will have problems. Why? After free (p), the
object is no longer valid, and free (p -> name) is meaningless and dangerous. Af-
ter calling free (p), the program cannot access the memory that contains the pointer
free (p -> name). There is no guarantee that the address is still accessible. If the destruc-
tor does not call free (p -> name), then the program leaks memory. Thus, as a general
rule, the destructor releases memory in the reverse order that the constructor allocates
memory. Please remember that every malloc must have a corresponding free.
Below is the implementation of the Person print function:
#in clude " person .h "1
#in clude < stdio .h >2
void Person _print ( Person * p )3
{4
printf (" Name : %s. " , p -> name );5
printf (" Date of Birth : %d /% d /% d n" ,6
p -> year , p -> month , p -> date );7
}8
The following is an example of using the constructor and the destructor:
#in clude < stdio .h >1
#in clude < stdlib .h >2
#in clude < string .h >3
#in clude " person .h "4
5
int main ( i n t argc , char * argv [])6
{7
Person * p1 = P erson _const ruct (1989 , 8, 21 , " Amy ") ;8
Person * p2 = P erson _const ruct (1991 , 2, 17 , " Bob ") ;9
Perso n_print ( p1 );10
Perso n_print ( p2 );11
Pers on_des truct ( p1 );12
Pers on_des truct ( p2 );13
return EXIT _SUCCES S ;14
}15
Some students struggle with the difference between objects and pointers to objects. A
pointer stores a memory address. Consider the following example. Please notice how p2 is
created.
Programmer-Defined Data Types 263
#in clude < stdio .h >1
#in clude < stdlib .h >2
#in clude < string .h >3
#in clude " person .h "4
5
void Person _print ( Person * p );6
int main ( i n t argc , char * argv [])7
{8
Person * p1 = P erson _const ruct (1989 , 8, 21 , " Amy ") ;9
Person * p2 = p1 ;10
Perso n_print ( p1 );11
Perso n_print ( p2 );12
Pers on_des truct ( p1 );13
Pers on_des truct ( p2 );14
return EXIT _SUCCES S ;15
}16
There is no syntax error in this program, but it contains a critical mistake. If we run this
program, it will likely crash. The problem reveals itself in Person destruct as reported by
valgrind:
==9344== Invalid read of size 8
==9344== at 0x400770: Person_destruct (persondestruct.c:6)
==9344== by 0x40069F: main (person2.c:14)
==9344== Address 0x51f2050 is 16 bytes inside a block of size 24 free’d
==9344== at 0x4C2A739: free
(in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==9344== by 0x400787: Person_destruct (persondestruct.c:7)
==9344== by 0x400693: main (person2.c:13)
What is wrong? To understand the problem, we need to understand what the assignment
means. After the main function finishes line 9, this is what is in the call stack and heap
memory:
Frame Symbol Address Value
main
p1 100 60000
Symbol Address Value
p1 -> name[3] 70003 0’
p1 -> name[2] 70002 ’y’
p1 -> name[1] 70001 ’m’
p1 -> name[0] 70000 ’A’
p1 -> name 60012 70000
p1 -> date 60008 21
p1 -> month 60004 8
p1 -> year 60000 1989
Line 10 assigns p1’s value to p2:
Frame Symbol Address Value
main
p2 104 60000
p1 100 60000
Both p1 and p2 point to the same memory address 60000. Line 13 calls the destructor
and releases the heap memory. This is perfectly correct. Line 14 calls the destructor again
but the memory has already been released. The same heap memory cannot be released
..................Content has been hidden....................

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