Anda di halaman 1dari 37

Programming Language Concepts Using C and C++/Generic Containers...

1 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

Programming Language Concepts Using C and


C++/Generic Containers
In this chapter, we will take a look at an implementation of a generic container, namely a vector. Our module
will enable its clients to create and manipulate vectors housing items of different data types. In doing so, we
will simulate the C++-style containers, which contain objects of a certain type.[1] That is, a particular
container will be either a collection of ints or a collection of doubles, but not a mixture of both. This is in
contrast with the pre-1.5 Java-style containers, where a container is a collection of Objects and therefore can
have items belonging to incompatible classes.
Another aspect of C programming that we will be discussing in this handout is the use of variadic functions
to compensate for the absence of overloading. Enabling the programmer to have an indefinite number of
unnamed formal parameters on top of the named ones, variadic functions can be used to provide a poor
mans substitute for overloading. All we have to do is to get the caller to provide an extra argument for
discrimination among overloaded function candidates.
Example: Instead of writing
void f(int); void f(int, int); void f(double);
f(3); f(3, 3); f(3.0);
we now write
void f(int func_type, ...);
f(ONE_INT, 3); f(TWO_INTS, 3, 3); f(ONE_DOUBLE, 3.0)
Our presentation will be concluded with a few words on static object module libraries.

Contents
1 Module
1.1 Interface
1.2 Implementation
2 Test Programs
2.1 Running the Test Program
3 Object Module Libraries
3.1 Library Header File
3.2 A Sample Module
3.3 Building the Library
4 Notes

Module
Interface
General.h

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

2 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

#ifndef GENERAL_H
#define GENERAL_H
...
...

Following macro definitions stand for constructor types that one should consider in the process of
implementing an abstract data type.

#define COPY 0
#define DEFAULT 1
...

Creating a copy of a Vector object from an already existing one involves copying its components too. With
a little insight we can see that there is no universal function to serve this purpose. As a matter of fact, same
data type may require different such functions in different contexts. Same argument applies to the case of
destroying a Vector object.
So, are we (the programmers) supposed to provide each and every possible copy and destruction functions?
This beats the purpose of providing a generic container. We have to get the user to cooperate with us. And
the way to do so is through the following pointer-to-function type definitions.

typedef Object (*COPY_FUNC)(Object);


typedef void (*DESTRUCTION_FUNC)(Object);
#endif

Vector.h

1.
#ifndef VECTOR_H
2.
#define VECTOR_H
3.

4.
#include "General.h"
5.

6.
struct _VECTOR;
7.
typedef struct _VECTOR* Vector;

We want to implement a generic vector. That is, a vector that can have anything as its component: a vector

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

3 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

of ints, a vector of character strings, a vector of student records, and so on. Given this requirement and the
fact that the structure of the component can be known only to the user [not the implementer], we need to
ask the user for some assistance about copying and destroying components of the vector. Basically, the user
must implement these two functions and somehow pass it to the implementer. In other words, the user and
the implementer must cooperate; the user must fill in the missing parts in the framework that is provided by
the implementer. A user maintaining a Vector of student records has to provide copy and destruction
functions for student record type; a user dealing with employee records has to provide the same functions for
employee record type; ...
This style of programming differs from the conventional technique, where the user of the library develops
algorithms that invoke library functions, but the overall structure of the application and flow of control
differs for each new application. Although almost any C program makes use of the I/O library, the overall
structure of a particular program is independent of others; routines provided in the I/O library are simply
'low-level' operations common to all programs and do not have to be tailored to the needs of a particular
application. This is the technique used with procedural programming languages such as Pascal and C.[2]

In software framework usage, however, it is the overall structure, the high-level view of the algorithm that
remains constant and is reused from application to application. The programmer (the user of the framework)
specializes the framework, but majority of code remains the same across all applications. Because of this
inversion in the relationship between library code and the user-supplied code, a software framework is
sometimes referred to as an "upside-down" library.

The best well-known software frameworks are those associated with graphical user interfaces. The major
tasks involved in executing a graphical program- waiting for the user to move or click the mouse, waiting for
keystrokes, moving or resizing windows, and so on- do not change much from one application to another.
Thus these actions can be usefully combined in a single framework not only to save programming effort in
developing new applications, but also to ensure consistency among applications. What distinguishes one
program from another are features such as the contents of windows and the action to be performed in
response to a mouse click or keystroke. It is these actions that must be programmed anew in each specific
application of the framework.
This is the style of choice in object-oriented programming languages. To create a new application, it is
necessary only to subclass from the original class [using inheritance] and then implement the behavior
associated with these deferred functions. All other functionality, including the overall structure of the
application, is obtained for free through the use of inheritance. Popular examples are the C++-based MFC

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

4 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

(Microsoft Foundation Classes) and Java-based JFC (Java Foundation Classes).


This style of programming (software framework) would be possible if the implementer could 'call back' to
some user code. In an object-oriented programming language, this can be done by implementing an interface
defined within the framework or subclassing an abstract class and implementing the abstract functionssuch
as mouse click, keystrokes, and etc.in this class. In C, we can do this by using function pointers. All we
have to do is to define the function signatures, which is what we are doing here, and use these definitions as
types of parameters in certain functions. The callback takes place in these functions by the implementer
calling the users code through the function pointer passed to the function.

9.
typedef struct _Object_funcs {
10.
COPY_FUNC copy_component;
11.
DESTRUCTION_FUNC destroy_component;
12.
} Vector_Component_Funcs;
13.

14.
#define NON_EXISTING_VECTOR -1
15.

16.
#define INIT_CAP 2
17.
#define BOTH 3

The following function signature is an example to the declaration of variadic functions in C. Ellipsis used in
the specification signifies an unknown number of parameters coming after the named arguments.[3] Number
of arguments passed to a variadic function is deduced from the contents of one or more of the named
arguments. Take the printf function for instance, whose prototype is provided below.
int printf(const char *format_string, ...);
Placeholders, certain character sequences starting with %, in format_string help in figuring out the number
and type of arguments.

19.
extern Vector Vector_Create(int constructor_type, ...);
20.
extern int Vector_Destroy(Vector* this);
21.
extern void Vector_Assign(Vector* lhs, const Vector rhs);
22.
extern Object Vector_ElementAt(const Vector this, unsigned int pos);

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

5 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

23.
extern Object Vector_InsertAt(const Vector this, Object item, unsigned int pos);
24.
extern Object Vector_RemoveAt(const Vector this, unsigned int pos);
25.
extern Object Vector_UpdateAt(const Vector this, Object new_item, unsigned int pos);
26.
extern unsigned int Vector_Size(const Vector this);
27.

28.
#endif

Implementation
Vector.c

1.
#include <stdarg.h>
2.
#include <stddef.h>
3.
#include <stdio.h>
4.
#include <stdlib.h>
5.
#include <string.h>
6.

7.
#include "General.h"
8.
#include "utility/Vector.h"

For controlling the lifetime of a particular object shared among different handles (actually pointers in our
case), we use an extra field to hold the number of handles that provide access to the same underlying object.
Such a field is called a reference count.
The last field of the structure definition can be seen, for pedagogical purposes, as Object
container[];. That is, a Vector is basically an array of Objects. Using * instead of [] gives us the
flexibility of changing the size of our Vector dynamically. Had we used [] we would have had to specify
the size at compile time, which would have been too restrictive for our purposes.

10.
struct _VECTOR {

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

6 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

11.
COPY_FUNC copy_component;
12.
DESTRUCTION_FUNC destroy_component;
13.
unsigned int ref_count;
14.
unsigned int cap;
15.
unsigned int cap_inc;
16.
unsigned int cur_size;
17.
Object *container;
18.
};
19.

20.
static BOOL adjust_capacity(const Vector);
21.
static void shift_down(const Vector, unsigned int);
22.
static void shift_up(const Vector, unsigned int);
23.

24.
Vector Vector_Create(int type, ...) {
25.
int i;

The following line declares a variable that will be used in traversing the list of unnamed arguments.

26.
va_list ap;
27.
Vector existing_vec;
28.
Vector_Component_Funcs comp_funcs;
29.

30.
Vector ret_vec = (Vector) malloc(sizeof(struct _VECTOR));
31.
if (!ret_vec) {
32.
fprintf(stderr, "Out of memory...\n");
33.
return NULL;
34.

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

7 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

} /* end of (!ret_vec) */
35.

36.
ret_vec->cur_size = 0;
37.
ret_vec->ref_count = 1;
38.
ret_vec->container = NULL;

The following macro is used to initialize the list of unnamed arguments so that an internal pointer in ap
points right after the last named argument.

40.
va_start(ap, type);
41.
switch (type) {
42.
case COPY:

Next line returns an argument of type Vector and advances the internal pointer so that it points to the
location right after this Vector argument. The run-time stack after execution of this statement is as shown
below:[4]

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

8 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

43.
existing_vec = va_arg(ap, Vector);

This is where we retrieve the function pointers passed by the user. Each time we need to copy or destroy a
component we will call back to the functions found at the given addresses.

44.
ret_vec->copy_component = existing_vec->copy_component;
45.
ret_vec->destroy_component = existing_vec->destroy_component;
46.
ret_vec->cap = ret_vec->cur_size = existing_vec->cur_size;
47.
ret_vec->cap_inc = existing_vec->cap_inc;

In addition to malloc, we can use calloc to allocate a region of memory from the heap. This function
allocates memory large enough to hold n (passed as the first argument) elements, each of size m (passed as
the second element); it returns a pointer to the first element of the array. This can certainly be done by
malloc. As an extra, the returned region is bitwise zeroed, which is not done by malloc.
Memory allocated calloc can be returned using free or cfree. But keep in mind that despite its
omnipresence cfree is not a standard C function.

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

9 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

Question:
Can we use calloc to allocate memory that holds an int with an initial value of 0? What about a
float with an initial value of 0.0? Can you guarantee that answer will always be the same?
float *f = (float *) calloc(1, sizeof(float));
/* What is the value contained in *f? Is it 0.0? */
int *i = (int *) calloc(1, sizeof(int));
/* What is the value contained in *i? Is it 0? */
Answer
Given the ubiquity of number representation standards the answer to the first part of question is very
likely to be affirmative. However, we still cannot give any guarantees.

48.
ret_vec->container = calloc(existing_vec->cur_size, sizeof(Object));
49.
for(i = 0; i < existing_vec->cur_size; i++)
50.
ret_vec->container[i] = existing_vec->copy_component(existing_vec->container[i]);
51.
break;
52.
case DEFAULT:
53.
comp_funcs = va_arg(ap, Vector_Component_Funcs);
54.
ret_vec->copy_component = comp_funcs.copy_component;
55.
ret_vec->destroy_component = comp_funcs.destroy_component;
56.
ret_vec->cap = ret_vec->cap_inc = 0;
57.
break;
58.
case INIT_CAP:
59.
comp_funcs = va_arg(ap, Vector_Component_Funcs);
60.
ret_vec->copy_component = comp_funcs.copy_component;
61.
ret_vec->destroy_component = comp_funcs.destroy_component;

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

10 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

62.
ret_vec->cap = va_arg(ap, unsigned int);
63.
ret_vec->cap_inc = 0;
64.
if (ret_vec->cap <= 0) break;
65.
ret_vec->container = calloc(ret_vec->cap, sizeof(Object));
66.
break;
67.
case BOTH:
68.
comp_funcs = va_arg(ap, Vector_Component_Funcs);
69.
ret_vec->copy_component = comp_funcs.copy_component;
70.
ret_vec->destroy_component = comp_funcs.destroy_component;
71.
ret_vec->cap = va_arg(ap, unsigned int);

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

11 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

72.
ret_vec->cap_inc = va_arg(ap, unsigned int);
73.
if (ret_vec->cap <= 0) break;
74.
ret_vec->container = calloc(ret_vec->cap, sizeof(Object));
75.
break;
76.
} /* end of switch(type) */

va_end performs necessary cleanup operations on ap. In other words, it serves the purpose of a destructor.
It should be called by the programmer after all arguments have been read with va_arg.

77.
va_end(ap);
78.

79.
return(ret_vec);

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

12 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

Question:
Which one of the following memory layouts is more reasonable for implementing variadic functions?
Answer
We know that named arguments and local variables are at fixed LB-relative addresses. Depending on
whether the run-time stack grows downward or upward, if arguments are at (LB offset) then local
variables are at (LB + offset); if arguments are at (LB + offset) then local variables are at (LB
offset). Such an arrangement enables the compiler to fix LB-relative addresses of these items at
compile-time. Now that memory reserved for unnamed arguments can vary depending on the number
and size of such items, the above scheme becomes impossible to guarantee with the choice on the right
side. So, the correct answer is right-to-left.
Insert figure
The names are due to the order of push operations. In right-to-left, first argument to be pushed on the
run-time stack is the rightmost argument in the argument list, while in left-to-right it is the leftmost that
is pushed first.[5]
For this reason, a programming language supporting variadic functions, such as C, will naturally use
right-to-left, while a language like Pascal, which doesnt support variadic functions, can choose either
arrangement. In a call to non-variadic function a C compiler may be (by means of a environment
specific directive) directed to pass the arguments in the opposite direction.[6]

Question:
In C, which one do you think is responsible for removing the arguments from the run-time stack, caller or
the callee?
Answer
Compiled code corresponding to the callee, just like any other code, is immutable. This means the code
template used for removing the arguments is fixed. However, the number of bytes pushed on the
runtime stack varies with the number of arguments and their types and this bit of information will
probably change for each invocation. This means the compiler cannot foresee the number of bytes
passed to the callee and synthesize a template for argument removal. For this reason, this template is
synthesized differently for each invocation and appended right after the point of call. So, it is the
callers responsibility to remove the arguments from the runtime stack.[7]

80.
} /* end of Vector Vector_Create(Constructor_type, struct basic_funcs, ...) */

The structure of a Vector object is as given below.


Insert figure

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

13 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

The user accesses a Vector object through a handle (this variable in the figure). The representation is
hidden and it is modified only through the functions in the public interface of Vector.
Note that the handle is a pointer to the properties of the Vector, not to its components. This is the typical
approach taken to implement collection types. You have a handle on some metadata, which (in addition to
defining properties of the collection) has a handle on the container that is used to hold the components.

82.
int Vector_Destroy(Vector* this) {
83.
int i;
84.
Vector this_copy = *this;
85.

86.
if (this_copy == NULL) return(NON_EXISTING_VECTOR);

The Vector_Destroy function having been called doesnt mean the underlying object will be destroyed.
In the case of an object that is shared among different handles, the reference count is decremented and the
underlying object is left untouched. If the reference count is one we are about to severe the tie between the
object and the last handle on it. In such a situation, we must also destroy the underlying object!

87.
if(this_copy->ref_count > 1) {
88.
*this = NULL;
89.
return(--this_copy->ref_count);
90.
} /* end of if(this_copy->ref_count > 1) */

Note that destroy_component is a pointer to function. That is, the function is called through a pointer;
we can change the function being invoked by assigning different pointer values. This is actually what gives
us the ability to deal with different types of data.[8]

92.
for (i = this_copy->cur_size - 1; i >= 0; i--)
93.
this_copy->destroy_component(this_copy->container[i]));
94.

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

14 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

95.
free(this_copy->container); free(this_copy);
96.
*this = NULL;
97.

98.
return 0;
99.
} /* end of int Vector_Destroy(Vector*) */
100.

101.
Object Vector_ElementAt(const Vector this, unsigned int pos) {
102.
Object ret_object;
103.

104.
if (pos >= this->cur_size) return NULL;

See that we return a copy of the required component, not a pointer to it. The rationale behind this is to avoid
giving any access to the underlying data structure.

105.
ret_object = this->copy_component(this->container[pos]);
106.

107.
return(ret_object);
108.
} /* end of Object Vector_ElementAt(const Vector, unsigned int) */

Next function assigns a Vector object to another. Since we dont have operator overloading in C, we have
to come up with a function name and use this name to perform assignment. Of course, we could still use '='
to do this. But, doing so would lead to an inconsistent state, where reference count and the number of
handles sharing the underlying object do not agree.

Vector v1, v2;


v1 = Vector_Create(DEFAULT, ...);
Vector_InsertAt(v1, ...); ...; ...; Vector_InsertAt(v1, ...); ...
...
Vector_Assign(&v2, v1);
...

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

15 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

The previous code fragment will produce the picture given below.
Insert figure

110.
void Vector_Assign(Vector* lhs, const Vector rhs{
111.
Vector_Destroy(lhs);
112.
*lhs = rhs;
113.
rhs->ref_count++;
114.
} /* end of void Vector_Assign(Vector*, const Vector) */
115.

116.
Object Vector_InsertAt(const Vector this, Object new_item, unsigned int pos) {
117.
if (this->cur_size < pos) pos = this->cur_size;

Before we make the insertion, we have to make sure there is enough room in our structure. If not, we adjust
the container capacity according to some predefined formula. In case we may be out of heap memory, we do
not effect any changes in the structure and just return a NULL value from the function. Otherwise, we extend
the memory region needed for the container and go ahead with the insertion: if the new component is
inserted at the end just copy it at the end of the vector, which is indicated by the cur_size field; if the new
component is inserted somewhere other than the end, shift the components that are to come after location of
the new item down by one and make the insertion.

118.
if (this->cap == this->cur_size)
119.
if (!adjust_capacity(this)) return NULL;
120.
if (pos != this->cur_size) shift_down(this, pos);
121.
this->container[pos] = this->copy_component(new_item);
122.
this->cur_size++;
123.

124.
return new_item;
125.
} /* end of Object Vector_InsertAt(const Vector, Object, unsigned int) */
126.

127.

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

16 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

Object Vector_RemoveAt(const Vector this, unsigned int pos) {


128.
Object ret_object;
129.

130.
if (pos >= this->cur_size) return NULL;
131.
ret_object = *(this->container + pos);
132.
if (pos != this->cur_size - 1) shift_up(this, pos);
133.
this->cur_size--;
134.

135.
return(ret_object);
136.
} /* end of Object Vector_RemoveAt(const Vector, unsigned int) */
137.

138.
Object Vector_UpdateAt(const Vector this, Object new_item, unsigned int pos) {
139.
Object old_value;
140.

141.
if (pos >= this->cur_size) return NULL;
142.

143.
old_value = *(this->container + pos);
144.
this->container[pos] = this->copy_component(new_value);
145.

146.
return old_value;
147.
} /* end of Object Vector_UpdateAt(const Vector, Object, unsigned int)*/
148.

149.
unsigned int Vector_Size(const Vector this) { return(this->cur_size); }

adjust_capacity function extends the capacity of a Vector according to some given formula. In our
implementation, we adopt the formula used in most C++ compilers: if the Vector has a nonzero capacity
increment value, capacity is incremented by this amount; if the user has not provided any value for this field
or it is zero, the new capacity is calculated by doubling the previous one. (If the current capacity value is
zero then new capacity is taken to be one.) Failure to allocate enough room for the Vector object results in
returning a FALSE value.

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

17 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

151.
static BOOL adjust_capacity(const Vector this) {
152.
int i;
153.
const Vector old_this = (Vector)

malloc(sizeof(struct _VECTOR));

154.
if(!old_this) {
155.
fprintf(stderr, "Out of memory...\n");
156.
return FALSE;
157.
} /* end of if(!old_this) */
158.
memcpy(old_this, this, sizeof(struct _VECTOR));
159.

160.
if (this->cap_inc != 0) this->cap += this->cap_inc;
161.
else if (this->cap == 0) this->cap = 1;
162.
else this->cap *= 2;
163.
this->container = calloc(this->cap, sizeof(Object));
164.

165.
if (!this->container) {
166.
this->cap = old_this->cap;
167.
this->container = old_this->container;
168.
free(old_this);
169.
fprintf(stderr, "Out of memory...\n");
170.
return FALSE;
171.
} /* end of if(!this->container) */
172.

173.
if (old_this->cap == 0) {
174.
free(old_this);
175.
return TRUE;
176.
} /* end of if(old_this->cap == 0) */

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

18 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

177.
memcpy(this->container, old_this->container,
178.
sizeof(Object) * old_this->cur_size);
179.

180.
free(old_this->container);
181.
free(old_this);
182.
return TRUE;
183.
} /* end of BOOL adjust_capacity(const Vector) */

Shifting down is required whenever a new item is inserted into any location other than the end of the
Vector.

185.
static void shift_down(const Vector this, unsigned int pos) {
186.
unsigned int i;
187.
for(i = this->cur_size; i > pos; i--)
188.
memcpy(this->container + i, this->container + (i - 1),sizeof(Object));
189.
} /* end of void shift_down(const Vector, unsigned int) */

Similarly, shifting up is required whenever an item is removed from any location other than the end of the
Vector.

191.
static void shift_up(const Vector this, unsigned int pos) {
192.
unsigned int i;
193.
for(i = pos; i < this->cur_size - 1; i++)
194.
memcpy(this->container + i, this->container + (i + 1), sizeof(Object));
195.
} /* end of void shift_up(const Vector, unsigned int) */

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

19 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

Test Programs
Vector_Test.c

1.
#include <stdio.h>
2.

3.
#include "General.h"
4.
#include "Wrappers.h"
5.
#include "utility/Vector.h"
6.

7.
enum {CREATE_COPY = 1, CREATE_DEFAULT, DESTROY, INSERT = 6, PEEK, REMOVE, UPDATE, DISPLAY, EXIT
8.

9.
Vector v[2];
10.

11.
void display_vec(unsigned int which) {
12.
unsigned int i;
13.

14.
if (!v[which]) {
15.
printf("-----\n");
16.
return;
17.
} /* end of if(!v[which]) */
18.
printf("+++++\n");
19.
printf("Contents of vector #%d\n", which);
20.
for (i = 0; i < Vector_Size(v[which]); i++)
21.
printf("#%d: %d\t", i, Integer_Value(Vector_ElementAt(v[which], i)));
22.
printf("\n");
23.

24.
return;
25.

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

20 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

} /* end of void display_vec(unsigned int) */


26.

27.
int main(void) {
28.
int val;
29.
Integer pval;
30.
unsigned int action, pos, which_vec, from_which_vec;
31.
Vector_Component_Funcs int_funcs = {&Integer_CopyInteger, &Integer_DestroyInteger};
32.

33.
do {
34.
scanf("%d", &action);
35.
switch (action) {
36.
case CREATE_COPY:
37.
scanf("%d %d", &which_vec, &from_which_vec);
38.
printf("Trying to create a copy of vector#%d into vector#%d...", from_which_vec, which_vec
39.
v[which_vec] = Vector_Create(COPY, v[from_which_vec]);
40.
if (v[which_vec]) printf("+++++\n");
41.
else printf("-----\n");
42.
break;
43.
case CREATE_DEFAULT:
44.
scanf("%d", &which_vec);
45.
printf("Trying to create vector #%d using default constructor...", which_vec);
46.
v[which_vec] = Vector_Create(DEFAULT, int_funcs );
47.
if (v[which_vec]) printf("+++++\n");
48.
else printf("-----\n");
49.
break;
50.
case DESTROY:
51.
scanf("%d", &which_vec);
52.
printf("Trying to destroy vector#%d...", which_vec);

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

21 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

53.
if (!Vector_Destroy(&v[which_vec])) printf("+++++\n");
54.
else printf("-----\n");
55.
break;
56.
case INSERT:
57.
scanf("%d %d %d", &which_vec, &pos, &val);
58.
printf("Trying to insert %d at position %d of vector#%d...", val, pos, which_vec);

Note we insert Integer objectswhich are basically objects of an opaque type that wrap around an intnot
ints. This is a consequence of the genericness provided by means of void*, which requires insertion of
pointers.

59.
pval = Integer_Create(val);
60.
if (Vector_InsertAt(v[which_vec], pval, pos)) printf("+++++\n");
61.
else printf("-----\n");
62.
break;

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

22 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

63.
case REMOVE:
64.
scanf("%d %d", &which_vec, &pos);
65.
printf("Trying to remove the item at position %d from vector#%d...", pos, which_vec);

Now that the value returned (ret_obj) will be assigned to pval, we will have a handle on the removed object
through pval.

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

23 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

66.
pval = Vector_RemoveAt(v[which_vec], pos);
67.
if (pval) printf("+++++%d\n", Integer_Value(pval));
68.
else printf("-----\n");
69.
break;
70.
case PEEK:
71.
scanf("%d %d", &which_vec, &pos);
72.
printf("Trying to peek in vector#%d at position %d...", which_vec, pos);

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

24 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

Realize the object is cloned and then returned. This new object, after assignment to pval, can be manipulated
independently of the original copy.

73.
pval = Vector_ElementAt(v[which_vec], pos);
74.
if (pval) printf("+++++%d\n", Integer_Value(pval));
75.
else printf("-----\n");
76.
break;
77.
case UPDATE:
78.
scanf("%d %d %d", &which_vec, &pos, &val);
79.
printf("Trying to update position %d of vector#%d with %d...", pos, which_vec, val);
80.
pval = Integer_Create(val);
81.
if (Vector_UpdateAt(v[which_vec], pval, pos))
82.
printf("+++++\n");
83.
else printf("-----\n");
84.
break;
85.
case DISPLAY:
86.
scanf("%d", &which_vec);

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

25 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

87.
printf("Trying to display vector#%d...", which_vec);
88.
display_vec(which_vec);
89.
break;
90.
case EXIT:
91.
exit(0);
92.
} /* end of switch(action) */
93.
} while (1);
94.
} /* end of int main(void) */

Running the Test Program


(Using Cygwin)
gcc c ID:/include Vector.c
gcc o Vector_Test.exe Vector_Test.c Vector.o lWrappers ID:/include LD:/library
The above command will bring into the executable the required object files found in the [static] library
named libWrappers.a. Name of the library to use is figured out by simply prefixing "lib" and suffixing ".a" to
the file name supplied with the l switch. Search for this library will make use of the directory passed with
the L switch.
Vector_Test < Vector_Test.input > Vector_Test.output
This command will redirect both input and output to disk files. It will still make the program believe that it is
receiving its input from the keyboard and sending its output to the screen. This becomes possible by
remapping stdin and stdout to different physical files. With no remapping, all processes in a system have the
following initial file table:
Per-process open file table
Physical file Other fields
0 Keyboard

...

1 Screen

...

2 Screen

...

After remapping we have:

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

26 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

Per-process open file table after


redirection
Physical file
Other fields
0 Vector_Test.input

...

1 Vector_Test.output ...
2 Screen

...

For both before and after remapping, we have the following macro definitions in place:

#define stdin 0
#define stdout 1
#define stderr 2

This is all done by the command-line interpreter. The user doesnt need to make any modifications in the
source code; (s)he can still program as if input were entered at the keyboard and output were written to the
screen. This is possible because we read from or write to a physical file through a handle (logical file). If we
change the physical file the handle is mapped to, although we write to the same logical file (handle) the final
destination affected changes.
Vector_Test.input

2 0
6 0 10 5
6 0 10 15
6 0 8 20
6 0 8 25
6 0 2 30
6 0 0 35
10 0
8 0 2
8 0 3
8 0 100
10 0
1 1 0
8 1 1
7 1 0
9 1 2 26
10 1
7 0 2
7 0 5
10 0
3 0
10 0
0

Vector_Test.output

Trying to create vector #0 using default constructor...+++++

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

27 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

Trying to insert 5 at position 10 of vector#0...+++++


Trying to insert 15 at position 10 of vector#0...+++++
Trying to insert 20 at position 8 of vector#0...+++++
Trying to insert 25 at position 8 of vector#0...+++++
Trying to insert 30 at position 2 of vector#0...+++++
Trying to insert 35 at position 0 of vector#0...+++++
Trying to display vector#0...+++++
Contents of vector #0
#0: 35 #1: 5
#2: 15 #3: 30 #4: 20 #5: 25
Trying to remove the item at position 2 from vector#0...+++++15
Trying to remove the item at position 3 from vector#0...+++++20
Trying to remove the item at position 100 from vector#0...----Trying to display vector#0...+++++
Contents of vector #0
#0: 35 #1: 5
#2: 30 #3: 25
Trying to create a copy of vector#0 into vector#1...+++++
Trying to remove the item at position 1 from vector#1...+++++5
Trying to peek in vector#1 at position 0...+++++35
Trying to update position 2 of vector#1 with 26...+++++
Trying to display vector#1...+++++
Contents of vector #1
#0: 35 #1: 30 #2: 25
Trying to peek in vector#0 at position 2...+++++30
Trying to peek in vector#0 at position 5...----Trying to display vector#0...+++++
Contents of vector #0
#0: 35 #1: 5
#2: 30 #3: 25
Trying to destroy vector#0...+++++
Trying to display vector#0...-----

Object Module Libraries


Whether you use them or not, linking statically with an object module brings in all the functionality within
the module. This means code you never use in your program will be there to increase size of the executable.
One can avoid this by splitting the contents of the module into smaller chunks. Using these smaller modules
will probably reduce the size of the executable but you now face another problem: having to write the names
of the object modules separately on the command line.[9]
Libraries offer a combination of both worlds.[10] Linking with a library means all external entities within the
individual modules are visible. However, only the modules that are used are brought into the executable.
Switching from one module to another in the library does not force you to change the command line.
Everything is taken care of by the linker and the library manager.
Example: Suppose libWrappers.a contains Integer.o, Char.o, Short.o, and so on. Having used some
functionality from Integer.o, the following command will cause only Integer.o to be brought in.
gcc o Vector_Test.exe Vector_Test.c Vector.o lWrappers ID:/include LD:/library
If we later decide to use some function from, say, Char.o, we dont need to make any modifications on the
command line. The linker-library manager pair will sort out the details for us and bring in only Char.o and
Integer.o.

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

28 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

Definition: A library is an archive of object modules, whichin addition to the modulesgenerally has
some sort of metadata to quicken locating the individual files.

Library Header File


Wrappers.h includes forward declarations and prototypes required for boxing primitive type data and
unboxing wrapped data. Note same thing could have been done by placing relevant information in separate
header files. There is no rule saying that one must have a single header file for an entire library. As a matter
of fact, you can have a separate header file for each and every object module [or even function] in the
library. Choosing to have a separate header file for each object module, however, has a disadvantage: As you
add a new function call in your code you have to figure out the header file it comes from and [probably] add
a relevant include directive, which is a reincarnation of the problem of having to write multiple object files
on the command line.
In some cases, striking a balance between the two extremes might be a better choice. Take, for instance, a
math library that offers tens, if not hundreds, of functions. Sifting through a single header file in such a case
is admittedly a painstaking process. Having multiple headers corresponding to different aspects of the math
library might be a good idea.
Wrappers.h

1.
#ifndef WRAPPERS_H
2.
#define WRAPPERS_H
3.

4.
#include "General.h"

Since our generic vector expects an opaque pointer as its component, using it with primitive data may turn
out to be painful: You must first wrap the data and use the pointer to this wrapped data in your transactions.
Later on when you want to print the data to some medium, you must do some unboxingthat is, retrieve the
contents of the region pointed by the opaque pointerand then print the data. Similar things can be said
about comparing, copying, and destroying the data.
This pattern is so common that a special library (libWrappers.a) is offered for the users convenience. In case
(s)he may want to use the Vector module and the like with primitive data types, (s)he doesnt have to
reinvent the wheel; all (s)he has to do is to use the functions found in libWrappers.a.
The whole thing can be likened to the wrapper classes in Java and the concept of automatic boxing-unboxing
in C#. As a matter of fact, this way of using a container is so frequent that Java, as of 1.5, supports automatic
boxing-unboxing.

6.
struct _CHAR;
7.
typedef struct _CHAR* Char;

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

29 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

8.
extern Char Char_Create(unsigned char val);
9.
extern void Char_Destroy(Char* this);
10.

11.
extern int Char_CompareChars(Object, Object);
12.
extern Object Char_CopyChar(Object);
13.
extern void Char_DestroyChar(Object);
14.

15.
extern unsigned char Char_Value(Char wrappedChar);
16.

17.
struct _SCHAR;
18.
typedef struct _SCHAR* SChar;
19.
extern SChar SChar_Create(signed char val);
20.
extern void SChar_Destroy(SChar* this);
21.

22.
extern int SChar_CompareSChars(Object, Object);
23.
extern Object SChar_CopySChar(Object);
24.
extern void SChar_DestroySChar(Object);
25.

26.
extern signed char SChar_Value(SChar wrappedSChar);
27.

28.
struct _INTEGER;
29.
typedef struct _INTEGER* Integer;
30.
extern Integer Integer_Create(signed int val);
31.
extern void Integer_Destroy(Integer* this);
32.

33.
extern int Integer_CompareIntegers(Object, Object);
34.
extern Object Integer_CopyInteger(Object);

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

30 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

35.
extern void Integer_DestroyInteger(Object);
36.

37.
extern signed int Integer_Value(Integer wrappedInt);
38.

39.
struct _UINTEGER;
40.
typedef struct _UINTEGER* UInteger;
41.
extern UInteger UInteger_Create(unsigned int val);
42.
extern void UInteger_Destroy(UInteger* this);
43.

44.
extern int UInteger_CompareUIntegers(Object, Object);
45.
extern Object UInteger_CopyUInteger(Object);
46.
extern void UInteger_DestroyUInteger(Object);
47.

48.
extern unsigned int UInteger_Value(UInteger wrappedUInt);
49.

50.
struct _LONG;
51.
typedef struct _LONG* Long;
52.
extern Long Long_Create(signed long val);
53.
extern void Long_Destroy(Long* this);
54.

55.
extern int Long_CompareLongs(Object, Object);
56.
extern Object Long_CopyLong(Object);
57.
extern void Long_DestroyLong(Object);
58.

59.
extern signed long Long_Value(Long wrappedLong);
60.

61.
struct _ULONG;
62.

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

31 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

typedef struct _ULONG* ULong;


63.
extern ULong ULong_Create(unsigned long val);
64.
extern void ULong_Destroy(ULong*);
65.

66.
extern int ULong_CompareULongs(Object, Object);
67.
extern Object ULong_CopyULong(Object);
68.
extern void ULong_DestroyULong(Object);
69.

70.
extern unsigned long ULong_Value(ULong wrappedULong);
71.

72.
struct _SHORT;
73.
typedef struct _SHORT* Short;
74.
extern Short Short_Create(signed short val);
75.
extern void Short_Destroy(Short*);
76.

77.
extern int Short_CompareShorts(Object, Object);
78.
extern Object Short_CopyShort(Object);
79.
extern void Short_DestroyShort(Object);
80.

81.
extern signed short Short_Value(Short wrappedShort);
82.

83.
struct _USHORT;
84.
typedef struct _USHORT* UShort;
85.
extern UShort UShort_Create(unsigned short val);
86.
extern void UShort_Destroy(UShort*);
87.

88.
extern int UShort_CompareUShorts(Object, Object);
89.
extern Object UShort_CopyUShort(Object);

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

32 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

90.
extern void UShort_DestroyUShort(Object);
91.

92.
extern unsigned short UShort_Value(UShort wrappedUShort);
93.

94.
struct _DOUBLE;
95.
typedef struct _DOUBLE* Double;
96.
extern Double Double_Create(double val);
97.
extern void Double_Destroy(Double*);
98.

99.
extern void Double_DestroyDouble(Object);
100.
extern int Double_CompareDoubles(Object, Object);
101.
extern Object Double_CopyDouble(Object);
102.

103.
extern double Double_Value(Double wrappedDouble);
104.

105.
struct _FLOAT;
106.
typedef struct _FLOAT* Float;
107.
extern Float Float_Create(float val);
108.
extern void Float_Destroy(Float*);
109.

110.
extern void Float_DestroyFloat(Object);
111.
extern int Float_CompareFloats(Object, Object);
112.
extern Object Float_CopyFloat(Object);
113.

114.
extern float Float_Value(Float wrappedFloat);
115.

116.
#endif

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

33 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

A Sample Module
What follows is an implementation of the wrapper interface for type int. Wrappers for the other primitive
types can be provided similarly and will not be included in this handout.
Integer.c

1.
#include <stddef.h>
2.
#include <stdio.h>
3.
#include <stdlib.h>
4.

5.
#include "Wrappers.h"
6.

7.
struct _INTEGER { signed int value; };
8.

9.
Integer Integer_Create(signed int val) {
10.
Integer this = (Integer) malloc(sizeof(struct _INTEGER));
11.
if (!this) {
12.
fprintf(stderr, "Out of memory...\n");
13.
return NULL;
14.
} /* end of if(!this) */
15.
this->value = val;
16.

17.
return this;
18.
} /* Integer Integer_Create(signed int) */
19.

20.
void Integer_Destroy(Integer* this) {
21.
Integer this_copy = *this;
22.
if (this_copy == NULL) return;

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

34 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

23.

24.
free(this_copy);
25.
*this = NULL;
26.
} /* end of void Integer_Destroy(Integer*) */
27.

28.
void Integer_DestroyInteger(Object this) {
29.
Integer_Destroy((Integer*) &this);
30.
} /* void Integer_DestroyInteger(Object) */
31.

32.
int Integer_CompareIntegers(Object this, Object rhs) {
33.
signed int thisValue = ((Integer) this)->value;
34.
signed int rhsValue = ((Integer) rhs)->value;
35.

36.
if (thisValue == rhsValue) return 0;
37.
else if (thisValue < rhsValue) return -1;
38.
else return 1;
39.
} /* int Integer_CompareIntegers(Object, Object)*/
40.

41.
Object Integer_CopyInteger(Object this) {
42.
Integer retVal = Integer_Create(((Integer) this)->value);
43.

44.
return retVal;
45.
} /* Object Integer_CopyInteger(Object) */
46.

47.
signed int Integer_Value(Integer wrappedInt) {
48.
return wrappedInt->value;
49.
} /* signed int Integer_Value(Integer) */

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

35 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

Building the Library


Using Cygwin
Before building the library we need to create the individual object modules that are to go into it.
gcc c Char.c ID:/include
gcc c Integer.c ID:/include
...
...
If all source files are in the same directory and no other files exist in that location, you can do it all in one
step:
gcc c *.c ID:/include
We can now create the libraryor archive in Unix lingoand append the object modules to it by the
following command. This will put all files ending with .o into libWrappers.a.
ar qc libWrappers.a *.o
the command line.

# For more on ar, simply type man ar on

If you are not convinced you may want to list the contents of the library.
ar t libWrappers.a
Char.o
Double.o
...
...
UShort.o
Now that libWrappers.a has been built, we can now place it in some fixed location and utilize the functions
residing in it as we did in our test program.
Using Windows
Before you issue the following commands make sure command-line tools are made available as external
commands. This may be easily done by executing [the commands contained in] the batch file named
vcvars32.bat found in the bin subdirectory of the Microsoft Visual C/C++ compiler.
cl /ID:/include /c /TC Char.c
cl /ID:/include /c /TC Integer.c
...
...
The preceding lines create the object files we need for building the library. Although rather short, they
underline the fact that we are using a compiler-driver, not a plain compiler. In order to successfully bring in
the required headers, using the /I option we pass the preprocessor information about places to look for
them. Thanks to the /c we also tell the compiler-driver to stop before linking. Finally, not really needed in
our example, using /TC we tell the compiler proper to treat the files passed on the command line as C source
code.
lib /OUT:Wrappers.lib Char.obj Integer.obj ...

# builds the library

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

36 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

lib /LIST Wrappers.lib


# lists the library contents
Microsoft (R) Library Manager Version 7.10.3077
Copyright ( C ) Microsoft Corporation. All rights reserved.
UShort.obj
ULong.obj
...
...
Char.obj
lib, Windows counterpart of ar, is the library-manager command. Passing different options we can get it to
build static libraries, list library contents, and so on.
cl /ID:/include /FeVector_Test.exe Vector.obj Wrappers.lib
Vector_Test.c
# To get help on options, pass /? to the cl
/D_CRT_SECURE_NO_DEPRECATE /link /NODEFAULTLIB:libc.lib
/DEFAULTLIB:libcmt.lib /LIBPATH:D:/library[11]
Another convincing proof that cl is actually a compiler-driver! In order to get Vector_Test.exe; preprocess
and compile Vector_Test.c and link the resulting object file with Vector.obj and needed object files from
Wrappers.lib, which can be found as a result of a search starting with the location given by means of the
/LIBPATH option passed to the linker.

Notes
1. With the introduction of inheritance, the same container might end up having items belonging to
different- but compatible- types.
2. Nevertheless, this doesnt mean that we cannot use other techniques.
3. Traditional C allows a variadic function with no named arguments while in Standard C there is a
minimum of one named argument.
4. Note that existing_vec is a local variable and therefore should appear in the lower part of the figure,
just below ap to be exact. The slot marked with existing_vec actually stands for the unnamed formal
parameter that corresponds to the second argument.
5. This can be used as an argument for why a compiler may choose not to evaluate actual parameters
from left-to-right. As a matter of fact, a right-to-left evaluation seems to be a better choice. After all, it
is the rightmost one that gets pushed onto the run-time stack first.
6. While perusing Windows source you may at times see a function name prefixed with specifiers such
as __cdecl (C calling convention), __stdcall (standard calling convention) or __pascal. Such a specifier
is used to tell the type of arrangement, naming convention and cleanup protocol used in the function
call. __cdecl is used with variadic functions, while __stdcall is used for the rest.
7. As a matter of fact, removing the arguments is usually no more than adjusting the stack pointer by a
certain amount.
8. One should also add the genericness provided through void*.
9. If you use a visual tool this would mean adding the object files to your project.
10. Depending on the time of linkage to the executable, a library can be either static or dynamic. The
former brings in the object module at link-time while in the latter this is deferred to the load- or
run-time. Our presentation in this handout will be limited to static libraries.
11. This post-2005 MS Visual Studio command line reflects a non-standard aspect of the Microsoft
compiler: in the name of making C a safer(!) programming language, compiler advisesby means of
warningsthe programmer to use scanf_s, which is a Microsoft-only function, instead of the
standard scanf. We ignore this by defining the _CRT_SECURE_NO_DEPRECATE macro. Starting
with MS Visual Studio 2005, Microsoft has also removed libc.lib- single-threaded static C runtimefrom its distribution. The default C runtime library that is now linked to form the executable is
libcmt.lib, which is the multi-threaded version of libc.lib. However, for some reason, the linker may
still end up looking for libc.lib! In order to fix this glaring glitch, using the /NODEFAULTLIB option,

20-11-2014 13:04

Programming Language Concepts Using C and C++/Generic Containers...

37 of 37

http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...

we remove libc.lib from the list of libraries searched in the process of resolving external references
and, using /DEFAULTLIB option, add libcmt.lib in its place. Therefore, in a pre-2005 MS Visual
Studio, this command line should be replaced with the following.
cl /ID:/include /FeVector_Test.exe Vector.obj Wrappers.lib
Vector_Test.c /link /LIBPATH:D:/library
Retrieved from "http://en.wikibooks.org
/w/index.php?title=Programming_Language_Concepts_Using_C_and_C%2B%2B/Generic_Containers&
oldid=2710822"

This page was last modified on 6 October 2014, at 21:42.


Text is available under the Creative Commons Attribution-ShareAlike License.; additional terms may
apply. By using this site, you agree to the Terms of Use and Privacy Policy.

20-11-2014 13:04

Anda mungkin juga menyukai